diff --git a/docs/readme.md b/docs/readme.md index 70f7bcf7..e2d5a37f 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -109,6 +109,12 @@ Support Release Notes ------------- +### Upcoming +- Changes + - Firebase AI: Add the AgentPlatform backend, and deprecate the VertexAI one, + to reflect the renaming of Vertex AI to Gemini Enterprise Agent Platform. + Note that the default location is different between the two. + ### 13.14.0 - Changes - General: Update to Firebase C++ SDK version 13.10.0. diff --git a/firebaseai/src/Citation.cs b/firebaseai/src/Citation.cs index 478978f0..10f99833 100644 --- a/firebaseai/src/Citation.cs +++ b/firebaseai/src/Citation.cs @@ -55,6 +55,7 @@ internal static CitationMetadata FromJson(Dictionary jsonDict, { FirebaseAI.Backend.InternalProvider.GoogleAI => "citationSources", FirebaseAI.Backend.InternalProvider.VertexAI => "citations", + FirebaseAI.Backend.InternalProvider.AgentPlatform => "citations", _ => throw new ArgumentOutOfRangeException(nameof(backend), backend, "Unsupported or unhandled backend provider encountered.") }; diff --git a/firebaseai/src/FirebaseAI.cs b/firebaseai/src/FirebaseAI.cs index 45080412..3be8b643 100644 --- a/firebaseai/src/FirebaseAI.cs +++ b/firebaseai/src/FirebaseAI.cs @@ -37,7 +37,8 @@ public readonly struct Backend internal enum InternalProvider { GoogleAI, - VertexAI + VertexAI, + AgentPlatform, } /// @@ -47,7 +48,7 @@ internal enum InternalProvider internal InternalProvider Provider { get; } /// /// Intended for internal use only. - /// The region identifier used by the Vertex AI backend. + /// The region identifier used by the Vertex AI and Gemini Enterprise Agent Platform backends. /// internal string Location { get; } @@ -68,9 +69,12 @@ public static Backend GoogleAI() /// /// The Vertex AI backend service configuration. /// - /// The region identifier, defaulting to `us-central1`; see [Vertex AI - /// regions](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#available-regions) - /// for a list of supported regions. + /// The region identifier, defaulting to `us-central1` + /// + /// Deprecated: Use AgentPlatform instead. Note that the default location changes to 'global'. + /// + /// @deprecated Use AgentPlatform instead. Note that the default location changes to 'global'. + [Obsolete("Use AgentPlatform instead. Note that the default location changes to 'global'.")] public static Backend VertexAI(string location = "us-central1") { if (string.IsNullOrWhiteSpace(location) || location.Contains("/")) @@ -82,6 +86,21 @@ public static Backend VertexAI(string location = "us-central1") return new Backend(InternalProvider.VertexAI, location); } + /// + /// The Gemini Enterprise Agent Platform backend service configuration. + /// + /// The region identifier, defaulting to `global` + public static Backend AgentPlatform(string location = "global") + { + if (string.IsNullOrWhiteSpace(location) || location.Contains("/")) + { + throw new ArgumentException( + $"The location argument must be non-empty, and not contain special characters like '/'"); + } + + return new Backend(InternalProvider.AgentPlatform, location); + } + public override readonly string ToString() { return $"FirebaseAIBackend|{Provider}|{Location}"; diff --git a/firebaseai/src/GenerativeModel.cs b/firebaseai/src/GenerativeModel.cs index f925524b..5a37d988 100644 --- a/firebaseai/src/GenerativeModel.cs +++ b/firebaseai/src/GenerativeModel.cs @@ -421,6 +421,7 @@ private string MakeCountTokensRequest(IEnumerable contents) $"models/{_modelName}"; break; case FirebaseAI.Backend.InternalProvider.VertexAI: + case FirebaseAI.Backend.InternalProvider.AgentPlatform: jsonDict = new() { // Convert the Contents into a list of Json dictionaries diff --git a/firebaseai/src/Internal/HttpHelpers.cs b/firebaseai/src/Internal/HttpHelpers.cs index 4db16723..310193b8 100644 --- a/firebaseai/src/Internal/HttpHelpers.cs +++ b/firebaseai/src/Internal/HttpHelpers.cs @@ -30,7 +30,8 @@ internal static class HttpHelpers internal static string GetURL(FirebaseApp firebaseApp, FirebaseAI.Backend backend, string modelName) { - if (backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) + if (backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI || + backend.Provider == FirebaseAI.Backend.InternalProvider.AgentPlatform) { return "https://firebasevertexai.googleapis.com/v1beta" + "/projects/" + firebaseApp.Options.ProjectId + @@ -54,7 +55,8 @@ internal static string GetTemplateURL(FirebaseApp firebaseApp, { var projectUrl = "https://firebasevertexai.googleapis.com/v1beta" + $"/projects/{firebaseApp.Options.ProjectId}"; - if (backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) + if (backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI || + backend.Provider == FirebaseAI.Backend.InternalProvider.AgentPlatform) { return $"{projectUrl}/locations/{backend.Location}/templates/{templateId}"; } diff --git a/firebaseai/src/LiveGenerativeModel.cs b/firebaseai/src/LiveGenerativeModel.cs index 80d13b1f..7c617457 100644 --- a/firebaseai/src/LiveGenerativeModel.cs +++ b/firebaseai/src/LiveGenerativeModel.cs @@ -76,7 +76,8 @@ internal LiveGenerativeModel(FirebaseApp firebaseApp, private string GetURL() { - if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) + if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI || + _backend.Provider == FirebaseAI.Backend.InternalProvider.AgentPlatform) { return "wss://firebasevertexai.googleapis.com/ws" + "/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent" + @@ -97,7 +98,8 @@ private string GetURL() private string GetModelName() { - if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) + if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI || + _backend.Provider == FirebaseAI.Backend.InternalProvider.AgentPlatform) { return $"projects/{_firebaseApp.Options.ProjectId}/locations/{_backend.Location}" + $"/publishers/google/models/{_modelName}"; diff --git a/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandler.cs b/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandler.cs index 754a1f8c..16699c1c 100644 --- a/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandler.cs +++ b/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandler.cs @@ -44,7 +44,7 @@ public class UIHandler : MonoBehaviour { private string appCheckDebugToken = "REPLACE_WITH_APP_CHECK_TOKEN"; - protected void InitializeAppCheck() { + protected virtual void InitializeAppCheck() { DebugLog("Initializing App Check directly in manual UIHandler"); DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugToken); FirebaseAppCheck.SetAppCheckProviderFactory(DebugAppCheckProviderFactory.Instance); @@ -76,11 +76,11 @@ protected void InitializeFirebase() { public string ModelName = "gemini-3.1-flash-lite"; private int backendSelection = 0; - private string[] backendChoices = new string[] { "Google AI Backend", "Vertex AI Backend" }; + private string[] backendChoices = new string[] { "Google AI Backend", "Agent Platform Backend" }; private GenerativeModel GetModel() { var backend = backendSelection == 0 ? FirebaseAI.Backend.GoogleAI() - : FirebaseAI.Backend.VertexAI(); + : FirebaseAI.Backend.AgentPlatform(); return FirebaseAI.GetInstance(backend, useLimitedUseAppCheckTokens: true).GetGenerativeModel(ModelName); } diff --git a/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandlerAutomated.cs b/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandlerAutomated.cs index 1e1fc3fd..a89791e7 100644 --- a/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandlerAutomated.cs +++ b/firebaseai/testapp/Assets/Firebase/Sample/FirebaseAI/UIHandlerAutomated.cs @@ -48,7 +48,7 @@ public class UIHandlerAutomated : UIHandler private string appCheckDebugTokenForAutomated = "REPLACE_WITH_APP_CHECK_TOKEN"; - protected void InitializeAppCheck() + protected override void InitializeAppCheck() { DebugLog("Initializing App Check directly in automated handler"); DebugAppCheckProviderFactory.Instance.SetDebugToken(appCheckDebugTokenForAutomated); @@ -63,7 +63,7 @@ protected void InitializeAppCheck() private enum Backend { GoogleAI, - VertexAI, + AgentPlatform, } // Set of status codes that are retryable. private readonly HashSet RetryableCodes = new HashSet { HttpStatusCode.TooManyRequests, HttpStatusCode.ServiceUnavailable, HttpStatusCode.GatewayTimeout }; @@ -205,7 +205,7 @@ protected override void Start() InternalTestFinishReasonSafetyNoContent, InternalTestUnknownEnumSafetyRatings, InternalTestFunctionCallWithArguments, - InternalTestVertexAIGrounding, + InternalTestAgentPlatformGrounding, InternalTestGoogleAIGrounding, InternalTestGoogleAIGroundingEmptyChunks, InternalTestMapsGrounding, @@ -321,7 +321,7 @@ private FirebaseAI GetFirebaseAI(Backend backend, string location = "global", bo return backend switch { Backend.GoogleAI => FirebaseAI.GetInstance(FirebaseAI.Backend.GoogleAI(), useLimitedUseAppCheckTokens), - Backend.VertexAI => FirebaseAI.GetInstance(FirebaseAI.Backend.VertexAI(location), useLimitedUseAppCheckTokens), + Backend.AgentPlatform => FirebaseAI.GetInstance(FirebaseAI.Backend.AgentPlatform(location), useLimitedUseAppCheckTokens), _ => throw new ArgumentOutOfRangeException(nameof(backend), backend, "Unhandled Backend type"), }; @@ -352,17 +352,17 @@ Task TestFirebaseAIInstanceCaching() Assert("GoogleAI: Instances with different limited use settings should be different.", googleAi1 != googleAi2); Assert("GoogleAI: Instances with the same limited use settings should be the same.", googleAi1 == googleAi3); - // Test within VertexAI - var vertexAi1 = GetFirebaseAI(Backend.VertexAI, useLimitedUseAppCheckTokens: false); - var vertexAi2 = GetFirebaseAI(Backend.VertexAI, useLimitedUseAppCheckTokens: true); - var vertexAi3 = GetFirebaseAI(Backend.VertexAI, useLimitedUseAppCheckTokens: false); + // Test within AgentPlatform + var agentPlatform1 = GetFirebaseAI(Backend.AgentPlatform, useLimitedUseAppCheckTokens: false); + var agentPlatform2 = GetFirebaseAI(Backend.AgentPlatform, useLimitedUseAppCheckTokens: true); + var agentPlatform3 = GetFirebaseAI(Backend.AgentPlatform, useLimitedUseAppCheckTokens: false); - Assert("VertexAI: Instances with different limited use settings should be different.", vertexAi1 != vertexAi2); - Assert("VertexAI: Instances with the same limited use settings should be the same.", vertexAi1 == vertexAi3); + Assert("AgentPlatform: Instances with different limited use settings should be different.", agentPlatform1 != agentPlatform2); + Assert("AgentPlatform: Instances with the same limited use settings should be the same.", agentPlatform1 == agentPlatform3); // Test cross-backend isolation - Assert("Instances of GoogleAI and VertexAI with the same settings should be different.", googleAi1 != vertexAi1); - Assert("Instances of GoogleAI and VertexAI with different settings should be different.", googleAi2 != vertexAi2); + Assert("Instances of GoogleAI and AgentPlatform with the same settings should be different.", googleAi1 != agentPlatform1); + Assert("Instances of GoogleAI and AgentPlatform with different settings should be different.", googleAi2 != agentPlatform2); return Task.CompletedTask; } @@ -1262,8 +1262,8 @@ async Task TestGoogleMaps(Backend backend) // Test providing a file from a GCS bucket (Firebase Storage) to the model. async Task TestReadFile() { - // GCS is currently only supported with VertexAI. - var model = CreateGenerativeModel(Backend.VertexAI); + // GCS is currently only supported with AgentPlatform. + var model = CreateGenerativeModel(Backend.AgentPlatform); GenerateContentResponse response = await model.GenerateContentAsync(new ModelContent[] { ModelContent.Text("I am testing File input. Can you describe the content in the attached file?"), @@ -1280,8 +1280,8 @@ async Task TestReadFile() // Should pass if Auth is included or not. To turn Auth on, define INCLUDE_FIREBASE_AUTH at the top of the file. async Task TestReadSecureFile() { - // GCS is currently only supported with VertexAI. - var model = CreateGenerativeModel(Backend.VertexAI); + // GCS is currently only supported with AgentPlatform. + var model = CreateGenerativeModel(Backend.AgentPlatform); #if INCLUDE_FIREBASE_AUTH var authResult = await FirebaseAuth.DefaultInstance.SignInAnonymouslyAsync(); @@ -1459,7 +1459,7 @@ private void ValidateCitation(Citation citation, async Task InternalTestBasicReplyShort() { Dictionary json = await GetVertexJsonTestData("unary-success-basic-reply-short.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); ValidateTextPart(response, "Mountain View, California"); @@ -1495,7 +1495,7 @@ Task InternalTestFinishReasonExpanded() }] }"; Dictionary json = (Dictionary)Json.Deserialize(jsonStr); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); Assert("Response missing candidates.", response.Candidates.Any()); Candidate candidate = response.Candidates.First(); @@ -1509,7 +1509,7 @@ Task InternalTestFinishReasonExpanded() }] }"; json = (Dictionary)Json.Deserialize(jsonStr); - response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); candidate = response.Candidates.First(); AssertEq("FinishReason", candidate.FinishReason, FinishReason.MalformedResponse); @@ -1650,7 +1650,7 @@ Task InternalTestSpeechConfigValidations() async Task InternalTestCitations() { Dictionary json = await GetVertexJsonTestData("unary-success-citations.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); ValidateTextPart(response, "Some information cited from an external source"); @@ -1682,7 +1682,7 @@ async Task InternalTestCitations() async Task InternalTestBlockedSafetyWithMessage() { Dictionary json = await GetVertexJsonTestData("unary-failure-prompt-blocked-safety-with-message.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); Assert("Candidates", !response.Candidates.Any()); Assert("Response.Text", string.IsNullOrEmpty(response.Text)); @@ -1713,7 +1713,7 @@ async Task InternalTestBlockedSafetyWithMessage() async Task InternalTestFinishReasonSafetyNoContent() { Dictionary json = await GetVertexJsonTestData("unary-failure-finish-reason-safety-no-content.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Candidate count", response.Candidates.Count(), 1); var candidate = response.Candidates.First(); @@ -1751,7 +1751,7 @@ async Task InternalTestFinishReasonSafetyNoContent() async Task InternalTestUnknownEnumSafetyRatings() { Dictionary json = await GetVertexJsonTestData("unary-success-unknown-enum-safety-ratings.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Candidate count", response.Candidates.Count(), 1); var candidate = response.Candidates.First(); @@ -1785,7 +1785,7 @@ async Task InternalTestUnknownEnumSafetyRatings() async Task InternalTestFunctionCallWithArguments() { Dictionary json = await GetVertexJsonTestData("unary-success-function-call-with-arguments.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Candidate count", response.Candidates.Count(), 1); var candidate = response.Candidates.First(); @@ -1801,13 +1801,13 @@ async Task InternalTestFunctionCallWithArguments() AssertEq("FunctionCall args[x] wrong value", fcPart.Args["x"], 4L); } - // Test that parsing a Vertex AI response with GroundingMetadata works. + // Test that parsing an Agent Platform response with GroundingMetadata works. // https://github.com/FirebaseExtended/vertexai-sdk-test-data/blob/main/mock-responses/vertexai/unary-success-google-search-grounding.json - async Task InternalTestVertexAIGrounding() + async Task InternalTestAgentPlatformGrounding() { Dictionary json = await GetVertexJsonTestData("unary-success-google-search-grounding.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); Assert("Response missing candidates.", response.Candidates.Any()); var candidate = response.Candidates.First(); @@ -1895,7 +1895,7 @@ async Task InternalTestMapsGrounding() { Dictionary json = await GetVertexJsonTestData("unary-success-google-maps-grounding.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); Assert("Response missing candidates.", response.Candidates.Any()); var candidate = response.Candidates.First(); @@ -1968,7 +1968,7 @@ async Task InternalTestCountTokenResponse() async Task InternalTestBasicResponseLongUsageMetadata() { Dictionary json = await GetVertexJsonTestData("unary-success-basic-response-long-usage-metadata.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Response Text", response.Text, "Here is a description of the image:\\n\\n"); @@ -1993,7 +1993,7 @@ async Task InternalTestBasicResponseLongUsageMetadata() async Task InternalTestUsageMetadataWithCaching() { Dictionary json = await GetVertexJsonTestData("unary-success-cached-content-usage-metadata.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("PromptTokenCount", response.UsageMetadata?.PromptTokenCount, 200); AssertEq("CandidatesTokenCount", response.UsageMetadata?.CandidatesTokenCount, 50); @@ -2180,7 +2180,7 @@ async Task InternalTestGenerateImagesBase64SomeFiltered() async Task InternalTestThoughtSummary() { Dictionary json = await GetVertexJsonTestData("unary-success-thinking-reply-thought-summary.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Response text", response.Text, "Mountain View"); @@ -2196,7 +2196,7 @@ async Task InternalTestThoughtSummary() async Task InternalTestCodeExecution() { Dictionary json = await GetVertexJsonTestData("unary-success-code-execution.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); AssertEq("Candidate count", response.Candidates.Count(), 1); var candidate = response.Candidates.First(); @@ -2222,7 +2222,7 @@ async Task InternalTestCodeExecution() async Task InternalTestUrlContextMixedValidity() { Dictionary json = await GetVertexJsonTestData("unary-success-url-context-mixed-validity.json"); - GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.VertexAI); + GenerateContentResponse response = GenerateContentResponse.FromJson(json, FirebaseAI.Backend.InternalProvider.AgentPlatform); Assert("Candidate should have UrlContextMetadata", response.Candidates.First().UrlContextMetadata.HasValue); var urlContextMetadata = response.Candidates.First().UrlContextMetadata.Value;