Intermediate

πŸ“¦ Structured Output

Get strongly-typed C# objects directly from the LLM β€” no manual JSON parsing required.

LLMs are excellent at extracting and transforming data. Structured output takes this further by constraining the model to return valid JSON that matches a specific schema β€” which Agent Framework automatically deserialises into a C# type.

The generic overload RunAsync<T>() accepts any class or C# record as the type parameter. Agent Framework generates a JSON Schema from the type and passes it to the model as a response format constraint. The model's output is then deserialised and returned as a strongly-typed instance of T.

This pattern is ideal for data extraction, classification, entity recognition, content generation in structured form, and any scenario where you need predictable, parse-safe output.

Key Concepts

  • RunAsync<T>() β€” generic overload that returns a deserialised T
  • C# records β€” concise syntax for defining the output schema
  • JSON Schema generation β€” Agent Framework generates the schema from your type automatically
  • Model constraints β€” the model is instructed to return only valid JSON matching the schema
  • [Description] attribute β€” annotate properties to improve model accuracy

NuGet Packages

dotnet add package Microsoft.Agents.AI.OpenAI --prerelease
dotnet add package Azure.AI.OpenAI --prerelease
dotnet add package Azure.Identity

Code Sample β€” Weather Forecast

using System.ComponentModel;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;

// 1. Define the output schema using a C# record.
//    Use [Description] attributes to guide the model for each property.
record WeatherForecast(
    [property: Description("The city name")]
    string City,
    [property: Description("The forecast high temperature in Celsius")]
    int HighCelsius,
    [property: Description("Short description of conditions, e.g. 'Sunny', 'Partly cloudy'")]
    string Conditions,
    [property: Description("Chance of rain as a percentage, 0-100")]
    int RainChancePercent
);

AIAgent agent = new AzureOpenAIClient(
        new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
        new AzureCliCredential())
    .GetChatClient("gpt-4o-mini")
    .AsAIAgent(instructions: "You are a weather data assistant. Always respond in valid JSON.");

// 2. Use the generic RunAsync<T> to get a strongly-typed result.
WeatherForecast forecast = await agent.RunAsync<WeatherForecast>(
    "What is the typical weather forecast for Seattle in July?");

// 3. Use the result like any other C# object β€” no JSON parsing needed.
Console.WriteLine($"City:        {forecast.City}");
Console.WriteLine($"High:        {forecast.HighCelsius}Β°C");
Console.WriteLine($"Conditions:  {forecast.Conditions}");
Console.WriteLine($"Rain chance: {forecast.RainChancePercent}%");

Code Sample β€” Data Extraction

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;

// Extract structured data from unstructured text.
record PersonInfo(string Name, int? Age, string? Email, string? Company);

AIAgent agent = new AzureOpenAIClient(
        new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
        new AzureCliCredential())
    .GetChatClient("gpt-4o-mini")
    .AsAIAgent(instructions: "Extract the requested information from the provided text. " +
                             "Use null for any fields not present in the text.");

string emailText = """
    Hi, I'm Sarah Johnson, 34 years old, and I work at Contoso Ltd.
    You can reach me at sarah.johnson@contoso.com for further details.
    """;

PersonInfo person = await agent.RunAsync<PersonInfo>(
    $"Extract person information from this text:\n\n{emailText}");

Console.WriteLine($"Name:    {person.Name}");
Console.WriteLine($"Age:     {person.Age}");
Console.WriteLine($"Email:   {person.Email}");
Console.WriteLine($"Company: {person.Company}");

Step-by-Step Explanation

  1. Define a C# record (or class) β€” Each property maps to a field in the JSON output. C# records are ideal because they are concise and immutable. Use nullable types (string?, int?) for optional fields.
  2. Annotate with [Description] β€” Adding descriptions to properties helps the model understand what each field represents, improving accuracy.
  3. Call RunAsync<T>() β€” Agent Framework generates a JSON Schema from your type, constrains the model's output format, and deserialises the JSON response.
  4. Use the result β€” The returned object is a fully deserialised instance of your type. Access properties directly with full IntelliSense and type checking.

Next Steps