Microsoft Agent Framework Agents Created: 16 Feb 2026 Updated: 16 Feb 2026

Response Types in the Microsoft Agent Framework

When you call an agent in the Microsoft Agent Framework, the response carries all content the agent produced — not just the final answer. This includes text results, function tool calls, function results, usage data, reasoning text, and more. Understanding how to navigate these response types is essential to building robust agent-powered applications.

Why Does This Matter?

Consider a scenario where the agent calls a weather API tool before answering your question. The response will contain:

  1. A FunctionCallContent — the agent’s request to call the tool
  2. A FunctionResultContent — the result from that tool call
  3. A TextContent — the actual answer the user sees
  4. A UsageContent — token consumption data

If you treat the entire response as the “answer,” you’ll get noise. The framework gives you a convenient .Text property that aggregates only the TextContent items, but you can also inspect every content item individually.

Non-Streaming: AgentResponse

RunAsync waits for the model to finish generating and returns a single AgentResponse object. This is the simplest approach.

Getting the Text Result

AgentResponse response = await agent.RunAsync("What is the weather like in Amsterdam?");

// The easy way — .Text aggregates all TextContent from all messages
Console.WriteLine(response.Text);

Under the hood, .Text iterates over every ChatMessage in response.Messages, collects all TextContent items, and concatenates their text. You can do the same manually:

var manualText = new StringBuilder();
foreach (var msg in response.Messages)
{
foreach (var content in msg.Contents)
{
if (content is TextContent textContent)
{
manualText.Append(textContent.Text);
}
}
}
Console.WriteLine(manualText.ToString());
Console.WriteLine($"Matches? {manualText.ToString() == response.Text}"); // True

Inspecting Messages and Content Items

response.Messages gives you access to every ChatMessage produced during the agent’s run. Each message has a Role (e.g., assistant) and a Contents collection of typed items:

Console.WriteLine($"Total messages: {response.Messages.Count}");

foreach (var message in response.Messages)
{
Console.WriteLine($" Role : {message.Role}");
Console.WriteLine($" Items: {message.Contents.Count}");

foreach (var content in message.Contents)
{
switch (content)
{
case TextContent tc:
Console.WriteLine($" TextContent : {tc.Text}");
break;
case FunctionCallContent fc:
Console.WriteLine($" FunctionCallContent: {fc.Name}({fc.Arguments})");
break;
case FunctionResultContent fr:
Console.WriteLine($" FunctionResult : {fr.Result}");
break;
case UsageContent uc:
Console.WriteLine($" UsageContent : In={uc.Details.InputTokenCount}, Out={uc.Details.OutputTokenCount}");
break;
default:
Console.WriteLine($" {content.GetType().Name}");
break;
}
}
}

Metadata and Token Usage

// Response metadata
Console.WriteLine($"CreatedAt : {response.CreatedAt}");
Console.WriteLine($"ResponseId : {response.ResponseId}");

// Token usage
if (response.Usage is { } usage)
{
Console.WriteLine($"InputTokens : {usage.InputTokenCount}");
Console.WriteLine($"OutputTokens : {usage.OutputTokenCount}");
Console.WriteLine($"TotalTokens : {usage.TotalTokenCount}");
}

Streaming: AgentResponseUpdate

RunStreamingAsync returns an IAsyncEnumerable<AgentResponseUpdate>. Each update is a small chunk that may contain a text fragment, a function call, usage data, or other content. Text arrives token-by-token, giving the user a real-time experience.

Simple Streaming

await foreach (var update in agent.RunStreamingAsync("What is the weather like in Amsterdam?"))
{
// update.Text is the text portion of this single chunk
Console.Write(update.Text);
}
Console.WriteLine();

Most chunks carry a small piece of text. Some chunks may have an empty Text — they contain only metadata or non-text content items.

Detailed Chunk Inspection

Just like the non-streaming case, each AgentResponseUpdate has a Contents collection where you can pattern-match individual items:

int chunkIndex = 0;
int textChunks = 0;
int nonTextChunks = 0;
var fullText = new StringBuilder();

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?"))
{
// Accumulate text fragments
if (!string.IsNullOrEmpty(update.Text))
{
fullText.Append(update.Text);
textChunks++;
}

// Inspect content items
foreach (var content in update.Contents)
{
switch (content)
{
case TextContent tc:
Console.Write(tc.Text);
break;
case FunctionCallContent fc:
nonTextChunks++;
Console.Write($"\n [Chunk #{chunkIndex}] FunctionCall: {fc.Name}");
break;
case FunctionResultContent fr:
nonTextChunks++;
Console.Write($"\n [Chunk #{chunkIndex}] FunctionResult: {fr.Result}");
break;
case UsageContent uc:
nonTextChunks++;
Console.Write($"\n [Chunk #{chunkIndex}] Usage: In={uc.Details.InputTokenCount}, Out={uc.Details.OutputTokenCount}");
break;
}
}

chunkIndex++;
}

Console.WriteLine($"\nTotal chunks: {chunkIndex}");
Console.WriteLine($"Text chunks: {textChunks}, Non-text chunks: {nonTextChunks}");
Console.WriteLine($"Full assembled text:\n{fullText}");

Measuring Performance: Time-to-First-Token

One key advantage of streaming is lower perceived latency. While the total generation time is similar, the user sees the first token almost immediately. Here’s how to measure both:

// Non-streaming: user waits for the full response
var sw = System.Diagnostics.Stopwatch.StartNew();
var nsResponse = await agent.RunAsync(prompt);
sw.Stop();
Console.WriteLine($"Non-streaming total time: {sw.ElapsedMilliseconds} ms");

// Streaming: measure time-to-first-token
sw.Restart();
long firstTokenMs = 0;
var streamResult = new StringBuilder();

await foreach (var update in agent.RunStreamingAsync(prompt))
{
if (!string.IsNullOrEmpty(update.Text))
{
if (firstTokenMs == 0)
firstTokenMs = sw.ElapsedMilliseconds;
streamResult.Append(update.Text);
}
}
sw.Stop();

Console.WriteLine($"Streaming time-to-first-token: {firstTokenMs} ms");
Console.WriteLine($"Streaming total time: {sw.ElapsedMilliseconds} ms");

Content Types at a Glance

TypeWhat It RepresentsIncluded in .Text?
TextContentThe actual text answer from the modelYes
FunctionCallContentThe agent requesting a tool/function callNo
FunctionResultContentThe result returned from a tool/function callNo
UsageContentToken usage information (input/output counts)No

Non-Streaming vs Streaming: When to Use Which?

AspectNon-Streaming (RunAsync)Streaming (RunStreamingAsync)
Return typeAgentResponseIAsyncEnumerable<AgentResponseUpdate>
First outputAfter full generation completesAs soon as the first token arrives
Best forProcessing, storing, or chaining resultsReal-time UI, chat interfaces, long responses
Text extractionresponse.TextAccumulate update.Text in a loop
Content inspectionmessage.Contents per messageupdate.Contents per chunk
Perceived latencyHigher (waits for complete response)Lower (first token arrives quickly)

Key Takeaways

  1. .Text is your shortcut — both AgentResponse and AgentResponseUpdate have a .Text property that extracts only the text result, filtering out tool calls, usage data, and other non-result content.
  2. Not all content is the answer — responses may include FunctionCallContent, FunctionResultContent, UsageContent, and more. Use pattern matching on .Contents to inspect them.
  3. Both approaches produce the same result — the only difference is how the response is delivered. Non-streaming returns everything at once; streaming delivers it chunk by chunk.
  4. Streaming reduces perceived latency — the user sees text appearing immediately instead of waiting for the full generation. This is especially valuable for long responses and real-time chat interfaces.


Share this lesson: