Skip to content

[Firebase AI] Add the test json files #1282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace Firebase.Sample.FirebaseAI {
using System.Threading.Tasks;
using Google.MiniJSON;
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.Networking;
using System.IO;
#if INCLUDE_FIREBASE_AUTH
using Firebase.Auth;
Expand Down Expand Up @@ -70,6 +70,8 @@ protected override void Start() {
TestCountTokens,
TestYoutubeLink,
TestGenerateImage,
TestImagenGenerateImage,
TestImagenGenerateImageOptions
};
// Set of tests that only run the single time.
Func<Task>[] singleTests = {
Expand All @@ -86,6 +88,9 @@ protected override void Start() {
InternalTestBasicResponseLongUsageMetadata,
InternalTestGoogleAIBasicReplyShort,
InternalTestGoogleAICitations,
InternalTestGenerateImagesBase64,
InternalTestGenerateImagesAllFiltered,
InternalTestGenerateImagesBase64SomeFiltered,
};

// Create the set of tests, combining the above lists.
Expand Down Expand Up @@ -443,7 +448,7 @@ async Task TestEnumSchemaResponse(Backend backend) {
generationConfig: new GenerationConfig(
responseMimeType: "text/x.enum",
responseSchema: Schema.Enum(new string[] { enumValue })));

var response = await model.GenerateContentAsync(
"Hello, I am testing setting the response schema to an enum.");

Expand All @@ -459,7 +464,7 @@ async Task TestAnyOfSchemaResponse(Backend backend) {
Schema.AnyOf(new[] { Schema.Int(), Schema.String() }),
minItems: 2,
maxItems: 6)));

var response = await model.GenerateContentAsync(
"Hello, I am testing setting the response schema with an array, cause you give me some random values.");

Expand Down Expand Up @@ -658,7 +663,7 @@ async Task TestYoutubeLink(Backend backend) {
async Task TestGenerateImage(Backend backend) {
var model = GetFirebaseAI(backend).GetGenerativeModel("gemini-2.0-flash-exp",
generationConfig: new GenerationConfig(
responseModalities: new [] { ResponseModality.Text, ResponseModality.Image })
responseModalities: new[] { ResponseModality.Text, ResponseModality.Image })
);

GenerateContentResponse response = await model.GenerateContentAsync(
Expand All @@ -683,6 +688,58 @@ async Task TestGenerateImage(Backend backend) {
Assert($"Missing expected modalities. Text: {foundText}, Image: {foundImage}", foundText && foundImage);
}

async Task TestImagenGenerateImage(Backend backend) {
var model = GetFirebaseAI(backend).GetImagenModel("imagen-3.0-generate-002");

var response = await model.GenerateImagesAsync(
"Generate an image of a cartoon dog.");

// We can't easily test if the image is correct, but can check other random data.
AssertEq("FilteredReason", response.FilteredReason, null);
AssertEq("Image Count", response.Images.Count, 1);

AssertEq($"Image MimeType", response.Images[0].MimeType, "image/png");

var texture = response.Images[0].AsTexture2D();
Assert($"Image as Texture2D", texture != null);
// By default the image should be Square 1x1, so check for that.
Assert($"Image Height > 0", texture.height > 0);
AssertEq($"Image Height = Width", texture.height, texture.width);
}

async Task TestImagenGenerateImageOptions(Backend backend) {
var model = GetFirebaseAI(backend).GetImagenModel(
modelName: "imagen-3.0-generate-002",
generationConfig: new ImagenGenerationConfig(
// negativePrompt and addWatermark are not supported on this version of the model.
numberOfImages: 2,
aspectRatio: ImagenAspectRatio.Landscape4x3,
imageFormat: ImagenImageFormat.Jpeg(50)
),
safetySettings: new ImagenSafetySettings(
safetyFilterLevel: ImagenSafetySettings.SafetyFilterLevel.BlockLowAndAbove,
personFilterLevel: ImagenSafetySettings.PersonFilterLevel.BlockAll),
requestOptions: new RequestOptions(timeout: TimeSpan.FromMinutes(1)));

var response = await model.GenerateImagesAsync(
"Generate an image of a cartoon dog.");

// We can't easily test if the image is correct, but can check other random data.
AssertEq("FilteredReason", response.FilteredReason, null);
AssertEq("Image Count", response.Images.Count, 2);

for (int i = 0; i < 2; i++) {
AssertEq($"Image {i} MimeType", response.Images[i].MimeType, "image/jpeg");

var texture = response.Images[i].AsTexture2D();
Assert($"Image {i} as Texture2D", texture != null);
// By default the image should be Landscape 4x3, so check for that.
Assert($"Image {i} Height > 0", texture.height > 0);
Assert($"Image {i} Height < Width {texture.height} < {texture.width}",
texture.height < texture.width);
}
}

// Test providing a file from a GCS bucket (Firebase Storage) to the model.
async Task TestReadFile() {
// GCS is currently only supported with VertexAI.
Expand Down Expand Up @@ -737,17 +794,43 @@ async Task TestReadSecureFile() {

// The url prefix to use when fetching test data to use from the separate GitHub repo.
readonly string testDataUrl =
"https://raw.githubusercontent.com/FirebaseExtended/vertexai-sdk-test-data/3737ae1fe9c5ecbd55abdeabc273ef4f392cbf19/mock-responses/";
"https://raw.githubusercontent.com/FirebaseExtended/vertexai-sdk-test-data/47becf9101d11ea3c568bf60b12f1c8ed9fb684e/mock-responses/";
readonly HttpClient httpClient = new();

private Task<string> LoadStreamingAsset(string fullPath) {
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
UnityWebRequest request = UnityWebRequest.Get(fullPath);
request.SendWebRequest().completed += (_) => {
if (request.result == UnityWebRequest.Result.Success) {
tcs.SetResult(request.downloadHandler.text);
} else {
tcs.SetResult(null);
}
};
return tcs.Task;
}

// Gets the Json test data from the given filename, potentially downloading from a GitHub repo.
private async Task<Dictionary<string, object>> GetJsonTestData(string filename) {
// TODO: Check if the file is available locally first
string jsonString = null;
// First, try to load the file from StreamingAssets
string localPath = Path.Combine(Application.streamingAssetsPath, "TestData", filename);
if (localPath.StartsWith("jar") || localPath.StartsWith("http")) {
// Special case to access StreamingAsset content on Android
jsonString = await LoadStreamingAsset(localPath);
} else if (File.Exists(localPath)) {
jsonString = File.ReadAllText(localPath);
}

var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, testDataUrl + filename));
response.EnsureSuccessStatusCode();

string jsonString = await response.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(jsonString)) {
DebugLog("Failed to load from local file, downloading...");
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, testDataUrl + filename));
response.EnsureSuccessStatusCode();

jsonString = await response.Content.ReadAsStringAsync();
} else {
DebugLog("Using local file");
}

return Json.Deserialize(jsonString) as Dictionary<string, object>;
}
Expand Down Expand Up @@ -862,7 +945,7 @@ async Task InternalTestBasicReplyShort() {
async Task InternalTestCitations() {
Dictionary<string, object> json = await GetVertexJsonTestData("unary-success-citations.json");
GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI);

ValidateTextPart(response, "Some information cited from an external source");

CitationMetadata? metadata = response.Candidates.First().CitationMetadata;
Expand Down Expand Up @@ -1050,7 +1133,7 @@ async Task InternalTestBasicResponseLongUsageMetadata() {
// Test that parsing a basic short reply from Google AI endpoint works as expected.
// https://github.com/FirebaseExtended/vertexai-sdk-test-data/blob/main/mock-responses/googleai/unary-success-basic-reply-short.txt
async Task InternalTestGoogleAIBasicReplyShort() {
Dictionary<string, object> json = await GetGoogleAIJsonTestData("unary-success-basic-reply-short.txt"); //
Dictionary<string, object> json = await GetGoogleAIJsonTestData("unary-success-basic-reply-short.json"); //
GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.GoogleAI);

ValidateTextPart(response, "Google's headquarters, also known as the Googleplex, is located in **Mountain View, California**.\n");
Expand All @@ -1076,12 +1159,12 @@ async Task InternalTestGoogleAIBasicReplyShort() {
// Test parsing a Google AI format response with citations.
// Based on: https://github.com/FirebaseExtended/vertexai-sdk-test-data/blob/main/mock-responses/googleai/unary-success-citations.txt
async Task InternalTestGoogleAICitations() {
Dictionary<string, object> json = await GetGoogleAIJsonTestData("unary-success-citations.txt");
Dictionary<string, object> json = await GetGoogleAIJsonTestData("unary-success-citations.json");
GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.GoogleAI);

// Validate Text Part (check start and end)
string expectedStart = "Okay, let's break down quantum mechanics.";
string expectedEnd = "foundation for many technologies, including:\n";
string expectedEnd = "area of physics!";
Assert("Candidate count", response.Candidates.Count() == 1);
Candidate candidate = response.Candidates.First();
AssertEq("Content role", candidate.Content.Role, "model");
Expand Down Expand Up @@ -1153,5 +1236,54 @@ async Task InternalTestGoogleAICitations() {
AssertEq("CandidatesTokensDetails[0].Modality", candidatesDetails[0].Modality, ContentModality.Text);
AssertEq("CandidatesTokensDetails[0].TokenCount", candidatesDetails[0].TokenCount, 1667);
}

async Task InternalTestGenerateImagesBase64() {
Dictionary<string, object> json = await GetVertexJsonTestData("unary-success-generate-images-base64.json");
var response = ImagenGenerationResponse<ImagenInlineImage>.FromJson(json);

AssertEq("FilteredReason", response.FilteredReason, null);
AssertEq("Image Count", response.Images.Count, 4);

for (int i = 0; i < response.Images.Count; i++) {
var image = response.Images[i];
AssertEq($"Image {i} MimeType", image.MimeType, "image/png");
Assert($"Image {i} Length: {image.Data.Length}", image.Data.Length > 0);

var texture = image.AsTexture2D();
Assert($"Failed to convert Image {i}", texture != null);
}
}

async Task InternalTestGenerateImagesAllFiltered() {
Dictionary<string, object> json = await GetVertexJsonTestData("unary-failure-generate-images-all-filtered.json");
var response = ImagenGenerationResponse<ImagenInlineImage>.FromJson(json);

AssertEq("FilteredReason", response.FilteredReason,
"Unable to show generated images. All images were filtered out because " +
"they violated Vertex AI's usage guidelines. You will not be charged for " +
"blocked images. Try rephrasing the prompt. If you think this was an error, " +
"send feedback. Support codes: 39322892, 29310472");
AssertEq("Image Count", response.Images.Count, 0);
}

async Task InternalTestGenerateImagesBase64SomeFiltered() {
Dictionary<string, object> json = await GetVertexJsonTestData("unary-failure-generate-images-base64-some-filtered.json");
var response = ImagenGenerationResponse<ImagenInlineImage>.FromJson(json);

AssertEq("FilteredReason", response.FilteredReason,
"Your current safety filter threshold filtered out 2 generated images. " +
"You will not be charged for blocked images. Try rephrasing the prompt. " +
"If you think this was an error, send feedback.");
AssertEq("Image Count", response.Images.Count, 2);

for (int i = 0; i < response.Images.Count; i++) {
var image = response.Images[i];
AssertEq($"Image {i} MimeType", image.MimeType, "image/png");
Assert($"Image {i} Length: {image.Data.Length}", image.Data.Length > 0);

var texture = image.AsTexture2D();
Assert($"Failed to convert Image {i}", texture != null);
}
}
}
}
8 changes: 8 additions & 0 deletions firebaseai/testapp/Assets/StreamingAssets.meta

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

8 changes: 8 additions & 0 deletions firebaseai/testapp/Assets/StreamingAssets/TestData.meta

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"candidates": [
{
"content": {
"parts": [
{
"text": "Google's headquarters, also known as the Googleplex, is located in **Mountain View, California**.\n"
}
],
"role": "model"
},
"finishReason": "STOP",
"safetyRatings": [
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"probability": "NEGLIGIBLE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"probability": "NEGLIGIBLE"
},
{
"category": "HARM_CATEGORY_HARASSMENT",
"probability": "NEGLIGIBLE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"probability": "NEGLIGIBLE"
}
],
"avgLogprobs": -0.048741644079034981
}
],
"usageMetadata": {
"promptTokenCount": 7,
"candidatesTokenCount": 22,
"totalTokenCount": 29,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 7
}
],
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 22
}
]
},
"modelVersion": "gemini-2.0-flash"
}

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

Loading
Loading