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

Producing Structured Output with Agents

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Why Use Structured Output?
  4. Core Concepts
  5. Step-by-Step: Creating a Structured Output Agent
  6. Demo 1 — Product Information Extraction
  7. Demo 2 — Event Planning
  8. Demo 3 — Recipe Parsing
  9. Demo 4 — Streaming with Structured Output
  10. Demo 5 — Complex Nested Structures
  11. Best Practices
  12. Debugging and Troubleshooting
  13. Summary

1. Introduction

Not all agent types support structured output. This lesson uses a ChatClientAgent, which does support structured output. The ChatClientAgent is built on top of any IChatClient implementation and uses the support for structured output that is provided by the underlying chat client.

When creating the agent, you have the option to provide a default ChatOptions instance to use for the underlying chat client. This ChatOptions instance allows you to pick a preferred ChatResponseFormat.

Getting structured output from AI agents you develop with the Microsoft Agent Framework enables you to obtain consistent and processable data in your applications. In this lesson, you will learn how to get outputs from agents that conform to a specific JSON schema. Instead of parsing free-form text, you will directly deserialize agent responses into strongly-typed C# objects.

2. Prerequisites

Before you begin, make sure you have:

  1. .NET 10 SDK installed
  2. An OpenAI API key (set the OPEN_AI_KEY environment variable)
  3. The following NuGet packages:
  4. Microsoft.Agents.AI
  5. Microsoft.Extensions.AI.OpenAI

3. Why Use Structured Output?

  1. Type Safety — AI outputs are directly deserialized into C# objects.
  2. Consistency — You always receive data in the same format.
  3. Validation — Data that does not conform to the schema is automatically rejected by the model.
  4. No Parsing Required — Direct object usage instead of text parsing.
  5. Easy Integration — Simple integration with databases, APIs, and other systems.

4. Core Concepts

4.1. ChatResponseFormat Types

Various options for ResponseFormat are available:

FormatDescriptionUse Case
ChatResponseFormat.TextThe response will be plain text.Normal chat, explanations, storytelling
ChatResponseFormat.JsonThe response will be a JSON object without any particular schema.Flexible JSON structures, dynamic data
ChatResponseFormat.ForJsonSchema()The response will be a JSON object that conforms to a specific schema.Structured data, when type safety is needed

4.2. AIJsonUtilities.CreateJsonSchema

The easiest way to produce the schema is to define a type that represents the structure of the output you want from the agent, and then use the AIJsonUtilities.CreateJsonSchema method to create a schema from the type. This eliminates the need to write JSON schemas manually.

public class ProductInfo
{
public string? Name { get; set; }
public decimal? Price { get; set; }
public List<string>? Features { get; set; }
}

// Auto-generate schema from the C# type
JsonElement schema = AIJsonUtilities.CreateJsonSchema(typeof(ProductInfo));

4.3. response.Deserialize<T>()

The agent response can then be deserialized into your class using the Deserialize<T> method on the response object.

var response = await agent.RunAsync("Extract product info...");
var product = response.Deserialize<ProductInfo>(JsonSerializerOptions.Web);

Console.WriteLine($"Product: {product.Name}");
Console.WriteLine($"Price: ${product.Price}");

5. Step-by-Step: Creating a Structured Output Agent

Step 1 — Define the Model Class

First, define the data structure you want as a C# class:

using System.Text.Json.Serialization;

public class ProductInfo
{
[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("brand")]
public string? Brand { get; set; }

[JsonPropertyName("price")]
public decimal? Price { get; set; }

[JsonPropertyName("category")]
public string? Category { get; set; }

[JsonPropertyName("features")]
public List<string>? Features { get; set; }
}
Note: Use [JsonPropertyName] attributes to control JSON field names. Use nullable types (string?, decimal?) for flexibility. Properties must be public with getters and setters.

Step 2 — Configure Schema with ChatOptions

You can then create a ChatOptions instance that uses this schema for the response format.

JsonElement schema = AIJsonUtilities.CreateJsonSchema(typeof(ProductInfo));

var chatOptions = new ChatOptions
{
ResponseFormat = ChatResponseFormat.ForJsonSchema(
schema: schema,
schemaName: "ProductInfo",
schemaDescription: "Product information with name, brand, price, category, and features")
};

Step 3 — Create the Agent

This ChatOptions instance can be used when creating the agent.

var chatClient = new OpenAIClient(apiKey)
.GetChatClient("gpt-4o")
.AsIChatClient();

var agent = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Name = "ProductExtractor",
Description = "Extracts structured product information from text.",
ChatOptions = chatOptions
});

Step 4 — Run the Agent and Deserialize

Now you can just run the agent with some textual information that the agent can use to fill in the structured output.

const string productText = """
The Dell XPS 15 is a premium laptop featuring an Intel Core i7,
16GB RAM, and 4K OLED display. Priced at $1,899.
""";

var response = await agent.RunAsync(productText);
var product = response.Deserialize<ProductInfo>(JsonSerializerOptions.Web);

Console.WriteLine($"Name: {product.Name}");
Console.WriteLine($"Brand: {product.Brand}");
Console.WriteLine($"Price: ${product.Price}");
Console.WriteLine($"Features: {string.Join(", ", product.Features ?? [])}");

6. Demo 1 — Product Information Extraction

This demo shows how to extract structured product information from unstructured text descriptions. The agent automatically identifies the product name, brand, price, category, and key features.

Use Cases:

  1. Product cataloging in e-commerce sites
  2. Price comparison applications
  3. Product data collection and analysis

The agent is configured with a ProductInfo schema and given two different product descriptions — a laptop and a smartphone. It parses each description into the same consistent structure.

var agent = StructuredOutputSetup.CreateStructuredAgent<ProductInfo>(
chatClient,
agentName: "ProductExtractor",
instructions: "You extract product information from text descriptions.",
schemaDescription: "Product information including name, brand, price, category, and features"
);

const string laptopText = """
The Dell XPS 15 is a premium laptop featuring an Intel Core i7 processor,
16GB RAM, and a stunning 4K OLED display. Priced at $1,899.
""";

var response = await agent.RunAsync(laptopText);
var product = response.Deserialize<ProductInfo>(JsonSerializerOptions.Web);

7. Demo 2 — Event Planning

This demo shows how to generate structured event plans from natural language descriptions. The agent extracts title, date, location, expected attendee count, budget, and a list of activities.

Use Cases:

  1. Corporate event management
  2. Training and seminar organization
  3. Social event planning assistants
public class EventPlan
{
[JsonPropertyName("title")]
public string? Title { get; set; }

[JsonPropertyName("date")]
public string? Date { get; set; }

[JsonPropertyName("location")]
public string? Location { get; set; }

[JsonPropertyName("attendees")]
public int? Attendees { get; set; }

[JsonPropertyName("budget")]
public decimal? Budget { get; set; }

[JsonPropertyName("activities")]
public List<string>? Activities { get; set; }
}

Given a natural language request like "I need to organize a team building event for 50 people with a $5,000 budget", the agent fills in all the fields consistently.

8. Demo 3 — Recipe Parsing

This demo shows how to parse recipe information from natural language descriptions into a structured format. The agent extracts recipe name, cuisine type, preparation time, servings, a list of ingredients, and cooking steps.

Use Cases:

  1. Recipe applications
  2. Diet and nutrition tracking
  3. Kitchen assistant chatbots
public class RecipeInfo
{
[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("cuisine")]
public string? Cuisine { get; set; }

[JsonPropertyName("preparation_time_minutes")]
public int? PreparationTimeMinutes { get; set; }

[JsonPropertyName("servings")]
public int? Servings { get; set; }

[JsonPropertyName("ingredients")]
public List<string>? Ingredients { get; set; }

[JsonPropertyName("steps")]
public List<string>? Steps { get; set; }
}

The demo provides two recipe descriptions — an Italian Carbonara and a Thai Green Curry — and the agent parses both into the same consistent structure.

9. Demo 4 — Streaming with Structured Output

When streaming, the agent response is streamed as a series of updates, and you can only deserialize the response once all the updates have been received. You must assemble all the updates into a single response before deserializing it.

Use Cases:

  1. Long-running data extraction operations
  2. Showing progress to users
  3. Real-time data visualization
// Stream and show raw JSON as it arrives
await foreach (var update in agent.RunStreamingAsync(text))
{
Console.Write(update.Text); // Show progress
}

// Use ToAgentResponseAsync() to assemble the full response, then deserialize
var finalResponse = await agent.RunStreamingAsync(text).ToAgentResponseAsync();
var product = finalResponse.Deserialize<ProductInfo>(JsonSerializerOptions.Web);

The key extension method is ToAgentResponseAsync(), which collects all streaming updates into a single AgentResponse that can then be deserialized.

10. Demo 5 — Complex Nested Structures

This demo shows how to work with complex nested structures containing multiple levels of objects and arrays. It extracts book information including nested author details and multiple user reviews.

Use Cases:

  1. Book and media cataloging
  2. Product reviews and ratings
  3. Hierarchical data structures
public class BookInfo
{
[JsonPropertyName("title")]
public string? Title { get; set; }

[JsonPropertyName("author")]
public AuthorInfo? Author { get; set; } // Nested object

[JsonPropertyName("publication_year")]
public int? PublicationYear { get; set; }

[JsonPropertyName("genres")]
public List<string>? Genres { get; set; }

[JsonPropertyName("reviews")]
public List<Review>? Reviews { get; set; } // Collection of nested objects
}

public class AuthorInfo
{
[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("nationality")]
public string? Nationality { get; set; }

[JsonPropertyName("birth_year")]
public int? BirthYear { get; set; }
}

public class Review
{
[JsonPropertyName("reviewer")]
public string? Reviewer { get; set; }

[JsonPropertyName("rating")]
public int? Rating { get; set; }

[JsonPropertyName("comment")]
public string? Comment { get; set; }
}

After deserialization, you can access nested data programmatically:

var book = response.Deserialize<BookInfo>(JsonSerializerOptions.Web);

Console.WriteLine($"Book: {book.Title}");
Console.WriteLine($"Author: {book.Author?.Name} ({book.Author?.Nationality})");
Console.WriteLine($"Average Rating: {book.Reviews?.Average(r => r.Rating ?? 0):F1}/5");

11. Best Practices

Do's

  1. Use clear and meaningful property names
  2. Specify snake_case JSON names with [JsonPropertyName]
  3. Provide flexibility by using nullable types
  4. Write detailed schema descriptions
  5. Add model validations (DataAnnotations)
  6. Implement error handling (try-catch)

Don'ts

  1. Don't create overly complex nested structures (more than 3 levels deep)
  2. Don't write conflicting schema definitions and instructions
  3. Don't request large lists or arrays (performance issues)
  4. Don't use the same schema for every agent — customize as needed

12. Debugging and Troubleshooting

Problem: Cannot deserialize the response.

Solution: Ensure JSON property names match your C# properties. Check [JsonPropertyName] usage.

Problem: Some fields are missing or null.

Solution: Write your schema description in more detail. Emphasize the importance of each field in the agent instructions.

Problem: Wrong types are returned.

Solution: Use nullable types and add custom JsonConverters where needed.

13. Summary

In this lesson, we learned the fundamentals of getting structured output with the Microsoft Agent Framework:

  1. Schema creation with AIJsonUtilities.CreateJsonSchema
  2. Agent configuration with ChatResponseFormat.ForJsonSchema
  3. Type conversion with response.Deserialize<T>()
  4. Streaming with structured output using ToAgentResponseAsync()
  5. Complex nested structures with multiple levels of objects and arrays

Useful Resources

  1. Official Documentation — Structured Output
  2. Microsoft Agent Framework GitHub
  3. Microsoft.Extensions.AI API Reference

Running the Application

# Set the OPEN_AI_KEY environment variable
$env:OPEN_AI_KEY = "your-api-key-here"

# Run the project
dotnet run

# Then try one of the demos from the menu



Share this lesson: