Skip to content

Commit 332e9f1

Browse files
authored
[Firebase AI] Improve Live API FunctionCalling (#1308)
* [Firebase AI] Improve Live API FunctionCalling * Update ModelContent.cs
1 parent b19079d commit 332e9f1

File tree

2 files changed

+56
-15
lines changed

2 files changed

+56
-15
lines changed

firebaseai/src/LiveSession.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ public async Task SendAsync(
103103
ModelContent? content = null,
104104
bool turnComplete = false,
105105
CancellationToken cancellationToken = default) {
106+
// If the content has FunctionResponseParts, we handle those separately.
107+
if (content.HasValue) {
108+
var functionParts = content?.Parts.OfType<ModelContent.FunctionResponsePart>().ToList();
109+
if (functionParts.Count > 0) {
110+
Dictionary<string, object> toolResponse = new() {
111+
{ "toolResponse", new Dictionary<string, object>() {
112+
{ "functionResponses", functionParts.Select(frPart => (frPart as ModelContent.Part).ToJson()["functionResponse"]).ToList() }
113+
}}
114+
};
115+
var toolResponseBytes = Encoding.UTF8.GetBytes(Json.Serialize(toolResponse));
116+
117+
await InternalSendBytesAsync(new ArraySegment<byte>(toolResponseBytes), cancellationToken);
118+
if (functionParts.Count < content?.Parts.Count) {
119+
// There are other parts to send, so send them with the other method.
120+
content = new ModelContent(role: content?.Role,
121+
parts: content?.Parts.Where(p => p is not ModelContent.FunctionResponsePart));
122+
} else {
123+
return;
124+
}
125+
}
126+
}
127+
106128
// Prepare the message payload
107129
Dictionary<string, object> contentDict = new() {
108130
{ "turnComplete", turnComplete }

firebaseai/src/ModelContent.cs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ public static ModelContent FileData(string mimeType, System.Uri uri) {
103103
/// `FunctionResponsePart` containing the given name and args.
104104
/// </summary>
105105
public static ModelContent FunctionResponse(
106-
string name, IDictionary<string, object> response) {
107-
return new ModelContent(new FunctionResponsePart(name, response));
106+
string name, IDictionary<string, object> response, string id = null) {
107+
return new ModelContent(new FunctionResponsePart(name, response, id));
108108
}
109109

110110
// TODO: Possibly more, like Multi, Model, FunctionResponses, System (only on Dart?)
@@ -236,22 +236,31 @@ Dictionary<string, object> Part.ToJson() {
236236
/// The function parameters and values, matching the registered schema.
237237
/// </summary>
238238
public IReadOnlyDictionary<string, object> Args { get; }
239+
/// <summary>
240+
/// An identifier that should be passed along in the FunctionResponsePart.
241+
/// </summary>
242+
public string Id { get; }
239243

240244
/// <summary>
241245
/// Intended for internal use only.
242246
/// </summary>
243-
internal FunctionCallPart(string name, IDictionary<string, object> args) {
247+
internal FunctionCallPart(string name, IDictionary<string, object> args, string id) {
244248
Name = name;
245249
Args = new Dictionary<string, object>(args);
250+
Id = id;
246251
}
247252

248253
Dictionary<string, object> Part.ToJson() {
254+
var jsonDict = new Dictionary<string, object>() {
255+
{ "name", Name },
256+
{ "args", Args }
257+
};
258+
if (!string.IsNullOrEmpty(Id)) {
259+
jsonDict["id"] = Id;
260+
}
261+
249262
return new Dictionary<string, object>() {
250-
{ "functionCall", new Dictionary<string, object>() {
251-
{ "name", Name },
252-
{ "args", Args }
253-
}
254-
}
263+
{ "functionCall", jsonDict }
255264
};
256265
}
257266
}
@@ -272,24 +281,33 @@ Dictionary<string, object> Part.ToJson() {
272281
/// The function's response or return value.
273282
/// </summary>
274283
public IReadOnlyDictionary<string, object> Response { get; }
284+
/// <summary>
285+
/// The id from the FunctionCallPart this is in response to.
286+
/// </summary>
287+
public string Id { get; }
275288

276289
/// <summary>
277290
/// Constructs a new `FunctionResponsePart`.
278291
/// </summary>
279292
/// <param name="name">The name of the function that was called.</param>
280293
/// <param name="response">The function's response.</param>
281-
public FunctionResponsePart(string name, IDictionary<string, object> response) {
294+
/// <param name="id">The id from the FunctionCallPart this is in response to.</param>
295+
public FunctionResponsePart(string name, IDictionary<string, object> response, string id = null) {
282296
Name = name;
283297
Response = new Dictionary<string, object>(response);
298+
Id = id;
284299
}
285300

286301
Dictionary<string, object> Part.ToJson() {
302+
var result = new Dictionary<string, object>() {
303+
{ "name", Name },
304+
{ "response", Response }
305+
};
306+
if (!string.IsNullOrEmpty(Id)) {
307+
result["id"] = Id;
308+
}
287309
return new Dictionary<string, object>() {
288-
{ "functionResponse", new Dictionary<string, object>() {
289-
{ "name", Name },
290-
{ "response", Response }
291-
}
292-
}
310+
{ "functionResponse", result }
293311
};
294312
}
295313
}
@@ -350,7 +368,8 @@ internal static class ModelContentJsonParsers {
350368
internal static ModelContent.FunctionCallPart FunctionCallPartFromJson(Dictionary<string, object> jsonDict) {
351369
return new ModelContent.FunctionCallPart(
352370
jsonDict.ParseValue<string>("name", JsonParseOptions.ThrowEverything),
353-
jsonDict.ParseValue<Dictionary<string, object>>("args", JsonParseOptions.ThrowEverything));
371+
jsonDict.ParseValue<Dictionary<string, object>>("args", JsonParseOptions.ThrowEverything),
372+
jsonDict.ParseValue<string>("id"));
354373
}
355374
}
356375

0 commit comments

Comments
 (0)