diff --git a/docs/readme.md b/docs/readme.md index 17529408..1b6800a4 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -109,6 +109,10 @@ Support Release Notes ------------- +### Upcoming +- Changes + - Firebase AI: Add support for Developer API backend to LiveSessions. + ### 13.0.0 - Changes - General: Update to Firebase C++ SDK version 13.0.0. diff --git a/firebaseai/src/FirebaseAI.cs b/firebaseai/src/FirebaseAI.cs index 09ea4258..5c1cead5 100644 --- a/firebaseai/src/FirebaseAI.cs +++ b/firebaseai/src/FirebaseAI.cs @@ -165,8 +165,6 @@ public GenerativeModel GetGenerativeModel( /// /// - Note: Refer to [Gemini models](https://firebase.google.com/docs/vertex-ai/gemini-models) for /// guidance on choosing an appropriate model for your use case. - /// - /// - Note: Currently only supports the VertexAI backend. /// /// The name of the model to use, for example `"gemini-2.0-flash-live-preview-04-09"`; see /// [available model names @@ -183,10 +181,6 @@ public LiveGenerativeModel GetLiveModel( Tool[] tools = null, ModelContent? systemInstruction = null, RequestOptions? requestOptions = null) { - if (_backend.Provider != Backend.InternalProvider.VertexAI) { - throw new NotSupportedException("LiveGenerativeModel is currently only supported with the VertexAI backend."); - } - return new LiveGenerativeModel(_firebaseApp, _backend, modelName, liveGenerationConfig, tools, systemInstruction, requestOptions); diff --git a/firebaseai/src/LiveGenerativeModel.cs b/firebaseai/src/LiveGenerativeModel.cs index 2035ff77..f5f31826 100644 --- a/firebaseai/src/LiveGenerativeModel.cs +++ b/firebaseai/src/LiveGenerativeModel.cs @@ -70,10 +70,30 @@ internal LiveGenerativeModel(FirebaseApp firebaseApp, } private string GetURL() { - return "wss://firebasevertexai.googleapis.com/ws" + - "/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent" + - $"/locations/{_backend.Location}" + - $"?key={_firebaseApp.Options.ApiKey}"; + if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) { + return "wss://firebasevertexai.googleapis.com/ws" + + "/google.firebase.vertexai.v1beta.LlmBidiService/BidiGenerateContent" + + $"/locations/{_backend.Location}" + + $"?key={_firebaseApp.Options.ApiKey}"; + } else if (_backend.Provider == FirebaseAI.Backend.InternalProvider.GoogleAI) { + return "wss://firebasevertexai.googleapis.com/ws" + + "/google.firebase.vertexai.v1beta.GenerativeService/BidiGenerateContent" + + $"?key={_firebaseApp.Options.ApiKey}"; + } else { + throw new NotSupportedException($"Missing support for backend: {_backend.Provider}"); + } + } + + private string GetModelName() { + if (_backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI) { + return $"projects/{_firebaseApp.Options.ProjectId}/locations/{_backend.Location}" + + $"/publishers/google/models/{_modelName}"; + } else if (_backend.Provider == FirebaseAI.Backend.InternalProvider.GoogleAI) { + return $"projects/{_firebaseApp.Options.ProjectId}" + + $"/models/{_modelName}"; + } else { + throw new NotSupportedException($"Missing support for backend: {_backend.Provider}"); + } } /// diff --git a/firebaseai/src/LiveSessionResponse.cs b/firebaseai/src/LiveSessionResponse.cs index bb99b9a6..43903912 100644 --- a/firebaseai/src/LiveSessionResponse.cs +++ b/firebaseai/src/LiveSessionResponse.cs @@ -58,7 +58,7 @@ public IReadOnlyList Audio { if (Message is LiveSessionContent content) { return content.Content?.Parts .OfType() - .Where(part => part.MimeType == "audio/pcm") + .Where(part => part.MimeType.StartsWith("audio/pcm")) .Select(part => part.Data.ToArray()) .ToList(); } diff --git a/firebaseai/src/ModelContent.cs b/firebaseai/src/ModelContent.cs index 29ad07a9..cee6cdbd 100644 --- a/firebaseai/src/ModelContent.cs +++ b/firebaseai/src/ModelContent.cs @@ -312,9 +312,9 @@ internal Dictionary ToJson() { /// This method is used for deserializing JSON responses and should not be called directly. /// internal static ModelContent FromJson(Dictionary jsonDict) { - // Both role and parts are required keys return new ModelContent( - jsonDict.ParseValue("role", JsonParseOptions.ThrowEverything), + // If the role is missing, default to model since this is likely coming from the backend. + jsonDict.ParseValue("role", defaultValue: "model"), // Unknown parts are converted to null, which we then want to filter out here jsonDict.ParseObjectList("parts", PartFromJson, JsonParseOptions.ThrowEverything).Where(p => p is not null)); }