Hi team,
I've been working with the streaming function calling feature in ChatSession recently and have encountered some behavior regarding the History log that I found a bit confusing. I was hoping to ask for some clarification here to see if my understanding is correct or if this might be an unexpected behavior.
.NET SDK:
Version: 9.0.100-rc.2.24474.11
Commit: 315e1305db
Workload version: 9.0.100-manifests.f0b34084
MSBuild version: 17.12.0-preview-24473-03+fea15fbd1
Runtime Environment:
OS Name: Mac OS X
OS Version: 26.0
OS Platform: Darwin
RID: osx-arm64
My Use Case
I'm trying to implement a multi-turn conversation that involves function calls.
Use Demo JavascriptCodeExecutor as an example:
I replaced :
while (true)
{
Console.WriteLine();
Console.WriteLine("Input: ");
var input = Console.ReadLine();
if(input == "restart")
goto restart;
var prompt =
$"\r\nExecute the javascript code for this task:\r\n\r\n{input}\r\n\r\n";
var request = new GenerateContentRequest();
request.AddText(input ?? "");
//Generate Response
var result = await chat.GenerateContentAsync(request).ConfigureAwait(false);
//Print Result
Console.WriteLine();
Console.Write("Result from Gemini:\r\n");
Console.WriteLine(result.Text());
}
With:
while (true)
{
Console.WriteLine();
Console.WriteLine("Input: ");
var input = Console.ReadLine();
if(input == "restart")
goto restart;
var prompt =
$"\r\nExecute the javascript code for this task:\r\n\r\n{input}\r\n\r\n";
var request = new GenerateContentRequest();
request.AddText(input ?? "");
//Generate Response Using Stream
await foreach (var response in chat.StreamContentAsync(request, default))
{
if (!string.IsNullOrEmpty(response.Text()))
{
Console.Write(response.Text());
}
}
}
And I found the same issue
My general workflow is:
First Turn: I send a request that requires a tool call (e.g., "calculate the sum from 1 to 69").
My app receives the functionCall from the model, executes it locally, and sends the functionResponse back.
I then use StreamContentAsync to wait for the model to stream back the final text-based answer.
Second Turn: After the first turn is fully complete, I proceed to ask a follow-up question.
Observed Behavior
What I've noticed is that after the first turn concludes, when I'm about to start the second turn, the internal History of the ChatSession seems to only contain the last two parts of the first interaction: the functionResponse and the model's final text answer.
The conversation history in the JSON request is disorganized and illogical, which becomes particularly problematic when I have many callable functions and the model performs multiple function calls in a single response.
As far as I know, Gemini requires the first or last entry in the contents array to have a specific role; otherwise, it will throw an error.
Raw Json Request/Response
First Run Request 1: input "calculate the sum from 1 to 69"
{
"contents": [
{
"parts": [
{
"text": "calculate the sum from 1 to 69"
}
],
"role": "user"
}
],
"tools": [
{
"functionDeclarations": [
{
"name": "ExecuteJavascriptCodeAsync",
...
First Run Response 1:
[
{
"candidates": [
{
"content": {
"parts": [
{
"functionCall": {
"name": "ExecuteJavascriptCodeAsync",
"args": {
"code": "let sum = 0; for (let i = 1; i <= 69; i++) { sum += i; } console.log(sum);"
}
}
}
],
"role": "model"
},
"finishReason": "STOP"
}
],
"usageMetadata": {
"promptTokenCount": 93,
"candidatesTokenCount": 39,
"totalTokenCount": 132,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 93
}
],
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 39
}
]
},
"modelVersion": "gemini-2.0-flash",
"responseId": "APd9aMzoJsuO7dcP9PjzmAY"
}
]
First Run Request 2:
{
"contents": [
{
"parts": [
{
"text": "calculate the sum from 1 to 69"
}
],
"role": "user"
},
{
"parts": [
{
"functionCall": {
"name": "ExecuteJavascriptCodeAsync",
"args": {
"code": "let sum = 0; for (let i = 1; i <= 69; i++) { sum += i; } console.log(sum);"
}
}
}
],
"role": "model"
},
{
"parts": [
{
"functionResponse": {
"name": "ExecuteJavascriptCodeAsync",
"response": {
"name": "ExecuteJavascriptCodeAsync",
"content": "2415\n"
}
}
}
],
"role": "function"
}
],
"tools": [
{
"functionDeclarations": [
{
"name": "ExecuteJavascriptCodeAsync",
...
First Run Response 2:
[
{
"candidates": [
{
"content": {
"parts": [
{
"text": "The"
}
],
"role": "model"
}
}
],
"usageMetadata": {
"promptTokenCount": 187,
"totalTokenCount": 187,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 187
}
]
},
"modelVersion": "gemini-2.0-flash",
"responseId": "Avd9aPaoJdWU7dcPv9bKkQs"
},
{
"candidates": [
{
"content": {
"parts": [
{
"text": " sum from 1 to 69 is 2415.\n"
}
],
"role": "model"
}
}
],
"usageMetadata": {
"promptTokenCount": 187,
"totalTokenCount": 187,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 187
}
]
},
"modelVersion": "gemini-2.0-flash",
"responseId": "Avd9aPaoJdWU7dcPv9bKkQs"
},
{
"candidates": [
{
"content": {
"parts": [
{
"text": ""
}
],
"role": "model"
},
"finishReason": "STOP"
}
],
"usageMetadata": {
"promptTokenCount": 147,
"candidatesTokenCount": 17,
"totalTokenCount": 164,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 147
}
],
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 17
}
]
},
"modelVersion": "gemini-2.0-flash",
"responseId": "Avd9aPaoJdWU7dcPv9bKkQs"
}
]
Second Run Request 1: input "calculate the sum from 1-42"
{
"contents": [
{
"parts": [//Weird History
{
"functionResponse": {
"name": "ExecuteJavascriptCodeAsync",
"response": {
"name": "ExecuteJavascriptCodeAsync",
"content": "2415\n"
}
}
}
],
"role": "function"
},
{
"parts": [
{
"text": "The\n sum from 1 to 69 is 2415.\n\n\n"
}
],
"role": "model"
},
{
"parts": [
{
"text": "calculate the sum from 1 to 69"
}
],
"role": "user"
},
{
"parts": [
{
"text": "\nThe\n sum from 1 to 69 is 2415.\n\n\n"
}
],
"role": "model"
},
{
"parts": [
{
"text": "calculate the sum from 1 to 42"
}
],
"role": "user"
}
],
"tools": [
{
"functionDeclarations": [
{
"name": "ExecuteJavascriptCodeAsync",
...
Hi team,
I've been working with the streaming function calling feature in ChatSession recently and have encountered some behavior regarding the History log that I found a bit confusing. I was hoping to ask for some clarification here to see if my understanding is correct or if this might be an unexpected behavior.
My Use Case
I'm trying to implement a multi-turn conversation that involves function calls.
My general workflow is:
First Turn: I send a request that requires a tool call (e.g., "calculate the sum from 1 to 69").
My app receives the functionCall from the model, executes it locally, and sends the functionResponse back.
I then use StreamContentAsync to wait for the model to stream back the final text-based answer.
Second Turn: After the first turn is fully complete, I proceed to ask a follow-up question.
Observed Behavior
What I've noticed is that after the first turn concludes, when I'm about to start the second turn, the internal History of the ChatSession seems to only contain the last two parts of the first interaction: the
functionResponseand the model's final text answer.The conversation history in the JSON request is disorganized and illogical, which becomes particularly problematic when I have many callable functions and the model performs multiple function calls in a single response.
As far as I know, Gemini requires the first or last entry in the contents array to have a specific role; otherwise, it will throw an error.
Raw Json Request/Response
First Run Request 1:
input "calculate the sum from 1 to 69"{ "contents": [ { "parts": [ { "text": "calculate the sum from 1 to 69" } ], "role": "user" } ], "tools": [ { "functionDeclarations": [ { "name": "ExecuteJavascriptCodeAsync", ...First Run Response 1:
[ { "candidates": [ { "content": { "parts": [ { "functionCall": { "name": "ExecuteJavascriptCodeAsync", "args": { "code": "let sum = 0; for (let i = 1; i <= 69; i++) { sum += i; } console.log(sum);" } } } ], "role": "model" }, "finishReason": "STOP" } ], "usageMetadata": { "promptTokenCount": 93, "candidatesTokenCount": 39, "totalTokenCount": 132, "promptTokensDetails": [ { "modality": "TEXT", "tokenCount": 93 } ], "candidatesTokensDetails": [ { "modality": "TEXT", "tokenCount": 39 } ] }, "modelVersion": "gemini-2.0-flash", "responseId": "APd9aMzoJsuO7dcP9PjzmAY" } ]First Run Request 2:
{ "contents": [ { "parts": [ { "text": "calculate the sum from 1 to 69" } ], "role": "user" }, { "parts": [ { "functionCall": { "name": "ExecuteJavascriptCodeAsync", "args": { "code": "let sum = 0; for (let i = 1; i <= 69; i++) { sum += i; } console.log(sum);" } } } ], "role": "model" }, { "parts": [ { "functionResponse": { "name": "ExecuteJavascriptCodeAsync", "response": { "name": "ExecuteJavascriptCodeAsync", "content": "2415\n" } } } ], "role": "function" } ], "tools": [ { "functionDeclarations": [ { "name": "ExecuteJavascriptCodeAsync", ...First Run Response 2:
[ { "candidates": [ { "content": { "parts": [ { "text": "The" } ], "role": "model" } } ], "usageMetadata": { "promptTokenCount": 187, "totalTokenCount": 187, "promptTokensDetails": [ { "modality": "TEXT", "tokenCount": 187 } ] }, "modelVersion": "gemini-2.0-flash", "responseId": "Avd9aPaoJdWU7dcPv9bKkQs" }, { "candidates": [ { "content": { "parts": [ { "text": " sum from 1 to 69 is 2415.\n" } ], "role": "model" } } ], "usageMetadata": { "promptTokenCount": 187, "totalTokenCount": 187, "promptTokensDetails": [ { "modality": "TEXT", "tokenCount": 187 } ] }, "modelVersion": "gemini-2.0-flash", "responseId": "Avd9aPaoJdWU7dcPv9bKkQs" }, { "candidates": [ { "content": { "parts": [ { "text": "" } ], "role": "model" }, "finishReason": "STOP" } ], "usageMetadata": { "promptTokenCount": 147, "candidatesTokenCount": 17, "totalTokenCount": 164, "promptTokensDetails": [ { "modality": "TEXT", "tokenCount": 147 } ], "candidatesTokensDetails": [ { "modality": "TEXT", "tokenCount": 17 } ] }, "modelVersion": "gemini-2.0-flash", "responseId": "Avd9aPaoJdWU7dcPv9bKkQs" } ]Second Run Request 1:
input "calculate the sum from 1-42"{ "contents": [ { "parts": [//Weird History { "functionResponse": { "name": "ExecuteJavascriptCodeAsync", "response": { "name": "ExecuteJavascriptCodeAsync", "content": "2415\n" } } } ], "role": "function" }, { "parts": [ { "text": "The\n sum from 1 to 69 is 2415.\n\n\n" } ], "role": "model" }, { "parts": [ { "text": "calculate the sum from 1 to 69" } ], "role": "user" }, { "parts": [ { "text": "\nThe\n sum from 1 to 69 is 2415.\n\n\n" } ], "role": "model" }, { "parts": [ { "text": "calculate the sum from 1 to 42" } ], "role": "user" } ], "tools": [ { "functionDeclarations": [ { "name": "ExecuteJavascriptCodeAsync", ...