Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Release Notes
as June 2026. As a replacement, you can
[migrate your apps to use Gemini Image models (the "Nano Banana" models)](https://firebase.google.com/docs/ai-logic/imagen-models-migration).
- Firebase AI: Add support for the JsonSchema formatting.
- Firebase AI: Add support for simplified object generation with GenerateObjectAsync.
- Firebase AI: Add support for TemplateChatSession.
- Functions: Rewrote internal serialization logic to C#. Removes dependency on internal C++ implementation.

Expand Down
57 changes: 57 additions & 0 deletions firebaseai/src/GenerateContentResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,63 @@ internal static GenerateContentResponse FromJson(Dictionary<string, object> json
}
}

/// <summary>
/// The model's response to a generate object request.
///
/// The object is deserialized using reflection. If you would like to implement your own
/// deserialization method, have your class T implement the `IFirebaseDeserializable` interface.
/// </summary>
/// <typeparam name="T">The type of the underlying Result</typeparam>
public readonly struct GenerateObjectResponse<T>
{
/// <summary>
/// The underlying `GenerateContentResponse` returned from the Model.
/// </summary>
public readonly GenerateContentResponse Response { get; }

/// <summary>
/// The deserialized object from the first Candidate response.
/// </summary>
public T Result => GetResult(0);

private readonly Dictionary<int, T> parsedResults;

/// <summary>
/// Intended for internal use. Call `GenerativeModel.GenerateObjectAsync`
/// to get a valid one.
/// </summary>
internal GenerateObjectResponse(GenerateContentResponse response)
{
Response = response;
parsedResults = new();
}

/// <summary>
/// Parse the resulting object from the requested candidate response.
/// </summary>
/// <param name="candidateIndex">The index of the candidate to parse.
/// Note that getting multiple candidates requires configuration settings.</param>
/// <returns>The deserialized object.</returns>
public T GetResult(int candidateIndex = 0)
{
if (parsedResults.TryGetValue(candidateIndex, out var t))
{
return t;
}

foreach (var part in Response.Candidates[candidateIndex].Content.Parts)
{
if (part is ModelContent.TextPart textPart && !textPart.IsThought)
{
T result = (T)SerializationHelpers.JsonStringToType(textPart.Text, typeof(T));
parsedResults[candidateIndex] = result;
return result;
}
}
return default;
}
}

/// <summary>
/// A type describing possible reasons to block a prompt.
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions firebaseai/src/GenerativeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,54 @@ public IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsync(
return GenerateContentStreamAsyncInternal(content, cancellationToken);
}

/// <summary>
/// Generates an object from input `ModelContent` given to the model as a prompt.
/// Note that this requires configuring the model to respond with a `Schema` or `JsonSchema`
/// that matches the type T.
/// </summary>
/// <typeparam name="T">The type of the object to generate.</typeparam>
/// <param name="content">The input given to the model as a prompt.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>The `GenerateObjectResponse` from the model, which contains the Result.</returns>
public async Task<GenerateObjectResponse<T>> GenerateObjectAsync<T>(
ModelContent content, CancellationToken cancellationToken = default)
{
var response = await GenerateContentAsync(content, cancellationToken);
return new GenerateObjectResponse<T>(response);
}

/// <summary>
/// Generates an object from input text given to the model as a prompt.
/// Note that this requires configuring the model to respond with a Schema or JsonSchema
/// that matches the type T.
/// </summary>
/// <typeparam name="T">The type of the object to generate.</typeparam>
/// <param name="text">The text given to the model as a prompt.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>The `GenerateObjectResponse` from the model, which contains the Result.</returns>
public async Task<GenerateObjectResponse<T>> GenerateObjectAsync<T>(
string text, CancellationToken cancellationToken = default)
{
var response = await GenerateContentAsync(text, cancellationToken);
return new GenerateObjectResponse<T>(response);
}

/// <summary>
/// Generates an object from input `ModelContent` given to the model as a prompt.
/// Note that this requires configuring the model to respond with a `Schema` or `JsonSchema`
/// that matches the type T.
/// </summary>
/// <typeparam name="T">The type of the object to generate.</typeparam>
/// <param name="content">The input given to the model as a prompt.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>The `GenerateObjectResponse` from the model, which contains the Result.</returns>
public async Task<GenerateObjectResponse<T>> GenerateObjectAsync<T>(
IEnumerable<ModelContent> content, CancellationToken cancellationToken = default)
{
var response = await GenerateContentAsync(content, cancellationToken);
return new GenerateObjectResponse<T>(response);
}

/// <summary>
/// Counts the number of tokens in a prompt using the model's tokenizer.
/// </summary>
Expand Down
173 changes: 173 additions & 0 deletions firebaseai/src/Serialization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright 2026 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;
using System.Collections.Generic;
using System.Reflection;
using Google.MiniJSON;

namespace Firebase.AI
{
/// <summary>
/// Interface to define a method to construct the object from a Dictionary<string, object>.
///
/// The Firebase AI Logic SDK by default will attempt to use reflection when deserializing objects,
/// like in `GenerateObjectResponse`, but this allows developers to override that logic with their own.
/// </summary>
public interface IFirebaseDeserializable
{
/// <summary>
/// Populate an object's fields with the given dictionary.
/// </summary>
/// <param name="dict">A deserialized Json blob, received from the underlying model.</param>
public void FromDictionary(IDictionary<string, object> dict);
}

namespace Internal
{
// Internal class that contains logic for serialization and deserialization.
internal static class SerializationHelpers
{
// Given a serialized Json string, tries to convert it to the given type.
internal static object JsonStringToType(string jsonString, Type type)
{
var resultDict = Json.Deserialize(jsonString);
return ObjectToType(resultDict, type);
}

// Given an object received from the model, tries to convert it to the given type.
internal static object ObjectToType(object obj, Type type)
{
if (obj == null) return null;

// If the type is an interface, try to convert it to something we can handle.
if (type.IsInterface)
{
if (type.IsGenericType)
{
Type genericDef = type.GetGenericTypeDefinition();
// Common interfaces to List<T>
if (genericDef == typeof(IEnumerable<>) ||
genericDef == typeof(IList<>) ||
genericDef == typeof(ICollection<>))
{
type = typeof(List<>).MakeGenericType(type.GetGenericArguments()[0]);
}
else if (type == typeof(IList) ||
type == typeof(IEnumerable))
{
type = typeof(List<object>);
}
}
}

// Convert the arg into the approriate type
if (obj is Type t)
{
return t;
}
else if (type.IsEnum)
{
if (obj is string str) return Enum.Parse(type, str);
return Enum.ToObject(type, obj);
}
else if (type.IsArray)
{
Type elementType = type.GetElementType();
if (obj is not System.Collections.IList inputList) return null;

Array array = Array.CreateInstance(elementType, inputList.Count);
for (int i = 0; i < inputList.Count; i++)
{
array.SetValue(ObjectToType(inputList[i], elementType), i);
}
return array;
}
else if (type.IsGenericType && typeof(IList).IsAssignableFrom(type))
{
if (obj is not IList inputList) return null;

Type elementType = type.GetGenericArguments()[0];

IList list = (IList)Activator.CreateInstance(type);

foreach (var input in inputList)
{
list.Add(ObjectToType(input, elementType));
}
return list;
}
else if (obj is Dictionary<string, object> dict)
{
return DictionaryToType(dict, type);
}

try
{
return Convert.ChangeType(obj, type);
}
catch
{
return obj;
}
}

// Given a Json style dictionary, tries to convert it to the given type.
internal static object DictionaryToType(Dictionary<string, object> dict, Type type)
{
object item = Activator.CreateInstance(type);

// Check the class for the interface, and use that if available.
if (item is IFirebaseDeserializable deserializable)
{
deserializable.FromDictionary(dict);
return item;
}

// Otherwise, fall back to reflection, which will be slower.
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
foreach (var kvp in dict)
{
try
{
// First, check for fields
FieldInfo field = type.GetField(kvp.Key, flags);
if (field != null)
{
field.SetValue(item, ObjectToType(kvp.Value, field.FieldType));
continue;
}

// Otherwise, check for properties
PropertyInfo prop = type.GetProperty(kvp.Key, flags);
if (prop != null && prop.CanWrite)
{
prop.SetValue(item, ObjectToType(kvp.Value, prop.PropertyType));
continue;
}
}
catch (Exception e)
{
UnityEngine.Debug.LogError($"Failed to convert object key {kvp.Key}, {e.Message}");
}
}

return item;
}
}
}
}
11 changes: 11 additions & 0 deletions firebaseai/src/Serialization.cs.meta

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

Loading