Skip to content

Commit b0224d6

Browse files
authored
[Firebase AI] Add support for TemplateChatSession (#1421)
* [Firebase AI] Add support for TemplateChatSession * Update TemplateTool.cs * Update TemplateTool.cs * Update TemplateTool.cs
1 parent e083450 commit b0224d6

File tree

6 files changed

+315
-7
lines changed

6 files changed

+315
-7
lines changed

docs/readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ Release Notes
115115
as June 2026. As a replacement, you can
116116
[migrate your apps to use Gemini Image models (the "Nano Banana" models)](https://firebase.google.com/docs/ai-logic/imagen-models-migration).
117117
- Firebase AI: Add support for the JsonSchema formatting.
118+
- Firebase AI: Add support for TemplateChatSession.
119+
- Functions: Rewrote internal serialization logic to C#. Removes dependency on internal C++ implementation.
118120

119121
### 13.9.0
120122
- Changes
@@ -125,7 +127,6 @@ Release Notes
125127
breaking change for custom providers, which must now implement the new
126128
`GetLimitedUseTokenAsync` method. If a custom provider does not support
127129
this feature, it can return a standard token from this method.
128-
- Functions: Rewrote internal serialization logic to C#. Removes dependency on internal C++ implementation.
129130

130131
### 13.8.0
131132
- Changes
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Linq;
20+
using System.Threading;
21+
using System.Threading.Tasks;
22+
using Firebase.AI.Internal;
23+
24+
namespace Firebase.AI
25+
{
26+
// Because of how the SDK is distributed, 'internal' isn't really internal
27+
// So hide the internal calls from generativeModel that are needed, they are passed in instead
28+
using GenerateContentFunc = Func<string, IDictionary<string, object>,
29+
IEnumerable<ModelContent>, IEnumerable<ITemplateTool>,
30+
TemplateToolConfig?, CancellationToken,
31+
Task<GenerateContentResponse>>;
32+
using GenerateContentStreamFunc = Func<string, IDictionary<string, object>,
33+
IEnumerable<ModelContent>, IEnumerable<ITemplateTool>,
34+
TemplateToolConfig?, CancellationToken,
35+
IAsyncEnumerable<GenerateContentResponse>>;
36+
37+
/// <summary>
38+
/// An object that represents a back-and-forth chat with a template model, capturing the history
39+
/// and saving the context in memory between each message sent.
40+
/// </summary>
41+
public class TemplateChatSession
42+
{
43+
private readonly TemplateGenerativeModel generativeModel;
44+
private readonly GenerateContentFunc generateContentFunc;
45+
private readonly GenerateContentStreamFunc generateContentStreamFunc;
46+
private readonly string templateId;
47+
private readonly Dictionary<string, object> inputs;
48+
private readonly List<ModelContent> chatHistory;
49+
private readonly List<ITemplateTool> tools;
50+
private readonly TemplateToolConfig? toolConfig;
51+
52+
/// <summary>
53+
/// The previous content from the chat that has been successfully sent and received from the
54+
/// model. This will be provided to the model for each message sent as context for the discussion.
55+
/// </summary>
56+
public IReadOnlyList<ModelContent> History => chatHistory;
57+
58+
// Note: No public constructor, get one through GenerativeModel.StartChat
59+
private TemplateChatSession(
60+
TemplateGenerativeModel model,
61+
GenerateContentFunc generateContentFunc,
62+
GenerateContentStreamFunc generateContentStreamFunc,
63+
string templateId,
64+
IDictionary<string, object> inputs,
65+
IEnumerable<ModelContent> initialHistory,
66+
IEnumerable<ITemplateTool> tools,
67+
TemplateToolConfig? toolConfig)
68+
{
69+
generativeModel = model;
70+
this.generateContentFunc = generateContentFunc;
71+
this.templateId = templateId;
72+
if (inputs != null)
73+
{
74+
this.inputs = new Dictionary<string, object>(inputs);
75+
}
76+
else
77+
{
78+
this.inputs = new Dictionary<string, object>();
79+
}
80+
chatHistory = initialHistory?.ToList() ?? new List<ModelContent>();
81+
this.tools = tools?.ToList() ?? new List<ITemplateTool>();
82+
this.toolConfig = toolConfig;
83+
}
84+
85+
/// <summary>
86+
/// Intended for internal use only.
87+
/// Use `TemplateGenerativeModel.StartChat` instead to ensure proper initialization.
88+
/// </summary>
89+
internal static TemplateChatSession InternalCreateChat(
90+
TemplateGenerativeModel model,
91+
GenerateContentFunc generateContentFunc,
92+
GenerateContentStreamFunc generateContentStreamFunc,
93+
string templateId,
94+
IDictionary<string, object> inputs,
95+
IEnumerable<ModelContent> initialHistory,
96+
IEnumerable<ITemplateTool> tools,
97+
TemplateToolConfig? toolConfig)
98+
{
99+
return new TemplateChatSession(
100+
model, generateContentFunc, generateContentStreamFunc, templateId, inputs,
101+
initialHistory, tools, toolConfig);
102+
}
103+
104+
/// <summary>
105+
/// Sends a message using the existing history of this chat as context.
106+
/// </summary>
107+
/// <param name="content">The input given to the model as a prompt.</param>
108+
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
109+
/// <returns>The model's response if no error occurred.</returns>
110+
public Task<GenerateContentResponse> SendMessageAsync(
111+
ModelContent content, CancellationToken cancellationToken = default)
112+
{
113+
return SendMessageAsyncInternal(content, cancellationToken);
114+
}
115+
116+
/// <summary>
117+
/// Sends a message using the existing history of this chat as context.
118+
/// </summary>
119+
/// <param name="text">The text given to the model as a prompt.</param>
120+
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
121+
/// <returns>The model's response if no error occurred.</returns>
122+
public Task<GenerateContentResponse> SendMessageAsync(
123+
string text, CancellationToken cancellationToken = default)
124+
{
125+
return SendMessageAsync(ModelContent.Text(text), cancellationToken);
126+
}
127+
128+
private async Task<GenerateContentResponse> SendMessageAsyncInternal(
129+
ModelContent content, CancellationToken cancellationToken)
130+
{
131+
// Make sure that the request is set to to role "user".
132+
ModelContent fixedRequest = FirebaseAIExtensions.ConvertToUser(content);
133+
// Set up the context to send in the history and request
134+
List<ModelContent> fullRequest = new(chatHistory) { fixedRequest };
135+
136+
// Note: GenerateContentAsync can throw exceptions if there was a problem, but
137+
// we allow it to just be passed back to the user.
138+
GenerateContentResponse response = await generateContentFunc(
139+
templateId, inputs, fullRequest, tools, toolConfig, cancellationToken);
140+
141+
// Only after getting a valid response, add both to the history for later.
142+
// But either way pass the response along to the user.
143+
if (response.Candidates.Any())
144+
{
145+
ModelContent responseContent = response.Candidates.First().Content;
146+
147+
chatHistory.Add(fixedRequest);
148+
chatHistory.Add(responseContent.ConvertToModel());
149+
}
150+
151+
return response;
152+
}
153+
}
154+
}

firebaseai/src/TemplateChatSession.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

firebaseai/src/TemplateGenerativeModel.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using System.Threading;
2222
using System.Threading.Tasks;
2323
using Google.MiniJSON;
24-
using Firebase.AI.Internal;
2524
using System.Linq;
2625
using System.Runtime.CompilerServices;
2726
using System.IO;
@@ -70,7 +69,7 @@ public Task<GenerateContentResponse> GenerateContentAsync(
7069
string templateId, IDictionary<string, object> inputs,
7170
CancellationToken cancellationToken = default)
7271
{
73-
return GenerateContentAsyncInternal(templateId, inputs, null, cancellationToken);
72+
return GenerateContentAsyncInternal(templateId, inputs, null, null, null, cancellationToken);
7473
}
7574

7675
/// <summary>
@@ -85,11 +84,33 @@ public IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsync(
8584
string templateId, IDictionary<string, object> inputs,
8685
CancellationToken cancellationToken = default)
8786
{
88-
return GenerateContentStreamAsyncInternal(templateId, inputs, null, cancellationToken);
87+
return GenerateContentStreamAsyncInternal(templateId, inputs, null, null, null, cancellationToken);
88+
}
89+
90+
/// <summary>
91+
/// Starts a <see cref="TemplateChatSession"/> that uses this template generative model to respond to messages.
92+
/// </summary>
93+
/// <param name="templateId">The id of the server prompt template to use.</param>
94+
/// <param name="inputs">Any input parameters expected by the server prompt template.</param>
95+
/// <param name="history">Optional chat history.</param>
96+
/// <param name="tools">Optional tools (e.g., auto functions) to use.</param>
97+
/// <param name="toolConfig">Optional tool configuration.</param>
98+
/// <returns>A new <see cref="TemplateChatSession"/> instance.</returns>
99+
public TemplateChatSession StartChat(
100+
string templateId,
101+
IDictionary<string, object> inputs = null,
102+
IEnumerable<ModelContent> history = null,
103+
IEnumerable<ITemplateTool> tools = null,
104+
TemplateToolConfig? toolConfig = null)
105+
{
106+
return TemplateChatSession.InternalCreateChat(
107+
this, GenerateContentAsyncInternal, GenerateContentStreamAsyncInternal,
108+
templateId, inputs, history, tools, toolConfig);
89109
}
90110

91111
private string MakeGenerateContentRequest(IDictionary<string, object> inputs,
92-
IEnumerable<ModelContent> chatHistory)
112+
IEnumerable<ModelContent> chatHistory,
113+
IEnumerable<ITemplateTool> tools, TemplateToolConfig? toolConfig)
93114
{
94115
var jsonDict = new Dictionary<string, object>()
95116
{
@@ -99,12 +120,22 @@ private string MakeGenerateContentRequest(IDictionary<string, object> inputs,
99120
{
100121
jsonDict["history"] = chatHistory.Select(t => t.ToJson()).ToList();
101122
}
123+
if (tools != null && tools.Any())
124+
{
125+
jsonDict["tools"] = tools.Select(t => t.ToJson()).ToList();
126+
}
127+
if (toolConfig.HasValue)
128+
{
129+
jsonDict["toolConfig"] = toolConfig.Value.ToJson();
130+
}
102131
return Json.Serialize(jsonDict);
103132
}
104133

105134
private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
106135
string templateId, IDictionary<string, object> inputs,
107136
IEnumerable<ModelContent> chatHistory,
137+
IEnumerable<ITemplateTool> tools,
138+
TemplateToolConfig? toolConfig,
108139
CancellationToken cancellationToken)
109140
{
110141
HttpRequestMessage request = new(HttpMethod.Post,
@@ -114,7 +145,7 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
114145
await Firebase.Internal.HttpHelpers.SetRequestHeaders(request, _firebaseApp);
115146

116147
// Set the content
117-
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory);
148+
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory, tools, toolConfig);
118149
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
119150

120151
#if FIREBASE_LOG_REST_CALLS
@@ -136,6 +167,8 @@ private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
136167
private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsyncInternal(
137168
string templateId, IDictionary<string, object> inputs,
138169
IEnumerable<ModelContent> chatHistory,
170+
IEnumerable<ITemplateTool> tools,
171+
TemplateToolConfig? toolConfig,
139172
[EnumeratorCancellation] CancellationToken cancellationToken)
140173
{
141174
HttpRequestMessage request = new(HttpMethod.Post,
@@ -145,7 +178,7 @@ private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsy
145178
await Firebase.Internal.HttpHelpers.SetRequestHeaders(request, _firebaseApp);
146179

147180
// Set the content
148-
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory);
181+
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory, tools, toolConfig);
149182
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
150183

151184
#if FIREBASE_LOG_REST_CALLS

firebaseai/src/TemplateTool.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Collections.Generic;
18+
using Firebase.AI.Internal;
19+
20+
namespace Firebase.AI
21+
{
22+
/// <summary>
23+
/// Tools that are intended to be used by server prompt templates.
24+
/// </summary>
25+
public interface ITemplateTool
26+
{
27+
#if !DOXYGEN
28+
/// <summary>
29+
/// Intended for internal use only.
30+
/// This method is used for serializing the object to JSON for the API request.
31+
/// </summary>
32+
Dictionary<string, object> ToJson();
33+
#endif
34+
}
35+
36+
/// <summary>
37+
/// Static class containing the various ITemplateTool's that are supported.
38+
/// </summary>
39+
public static class TemplateTool
40+
{
41+
/// <summary>
42+
/// A class representing a generic function declaration used with a template model.
43+
/// </summary>
44+
public class FunctionDeclaration : ITemplateTool
45+
{
46+
public string Name { get; }
47+
public JsonSchema JsonParameters { get; }
48+
49+
/// <summary>
50+
/// Constructs a TemplateTool.FunctionDeclaration
51+
/// </summary>
52+
/// <param name="name">The name of the function.</param>
53+
/// <param name="parameters">Dictionary of parameters schema.</param>
54+
/// <param name="optionalParameters">List of parameter names that are not required.</param>
55+
public FunctionDeclaration(string name,
56+
IDictionary<string, JsonSchema> parameters,
57+
IEnumerable<string> optionalParameters = null)
58+
{
59+
Name = name;
60+
JsonParameters = JsonSchema.Object(parameters, optionalParameters);
61+
}
62+
63+
/// <summary>
64+
/// Intended for internal use only.
65+
/// This method is used for serializing the object to JSON for the API request.
66+
/// </summary>
67+
Dictionary<string, object> ITemplateTool.ToJson()
68+
{
69+
var jsonDict = new Dictionary<string, object>()
70+
{
71+
{ "name", Name },
72+
{ "inputSchema", JsonParameters.ToJson() }
73+
};
74+
return new Dictionary<string, object>()
75+
{
76+
{ "templateFunctions", jsonDict }
77+
};
78+
}
79+
}
80+
}
81+
82+
/// <summary>
83+
/// Tool configuration for any TemplateTool specified in the request.
84+
/// </summary>
85+
public readonly struct TemplateToolConfig
86+
{
87+
// This is intentionally empty, but things will be added in the future.
88+
89+
/// <summary>
90+
/// Intended for internal use only.
91+
/// This method is used for serializing the object to JSON for the API request.
92+
/// </summary>
93+
internal IDictionary<string, object> ToJson()
94+
{
95+
return new Dictionary<string, object>();
96+
}
97+
}
98+
}

firebaseai/src/TemplateTool.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)