chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
namespace SampleApp;
/// <summary>
/// Provides extension methods for adding structured output capabilities to <see cref="AIAgentBuilder"/> instances.
/// </summary>
internal static class AIAgentBuilderExtensions
{
/// <summary>
/// Adds structured output capabilities to the agent pipeline, enabling conversion of text responses to structured JSON format.
/// </summary>
/// <param name="builder">The <see cref="AIAgentBuilder"/> to which structured output support will be added.</param>
/// <param name="chatClient">
/// The chat client used to transform text responses into structured JSON format.
/// If <see langword="null"/>, the chat client will be resolved from the service provider.
/// </param>
/// <param name="optionsFactory">
/// An optional factory function that returns the <see cref="StructuredOutputAgentOptions"/> instance to use.
/// This allows for fine-tuning the structured output behavior such as setting the response format or system message.
/// </param>
/// <returns>The <see cref="AIAgentBuilder"/> with structured output capabilities added, enabling method chaining.</returns>
/// <remarks>
/// <para>
/// A <see cref="ChatResponseFormatJson"/> must be specified either through the
/// <see cref="AgentRunOptions.ResponseFormat"/> at runtime or the <see cref="StructuredOutputAgentOptions.ChatOptions"/>
/// provided during configuration.
/// </para>
/// </remarks>
public static AIAgentBuilder UseStructuredOutput(
this AIAgentBuilder builder,
IChatClient? chatClient = null,
Func<StructuredOutputAgentOptions>? optionsFactory = null)
{
ArgumentNullException.ThrowIfNull(builder);
return builder.Use((innerAgent, services) =>
{
chatClient ??= services?.GetService<IChatClient>()
?? throw new InvalidOperationException($"No {nameof(IChatClient)} was provided and none could be resolved from the service provider. Either provide an {nameof(IChatClient)} explicitly or register one in the dependency injection container.");
return new StructuredOutputAgent(innerAgent, chatClient, optionsFactory?.Invoke());
});
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,185 @@
// Copyright (c) Microsoft. All rights reserved.
// Structured Output — Configure agents to return typed JSON
//
// This sample shows how to configure a ChatClientAgent to produce
// structured output using JSON schema constraints with Azure AI Foundry.
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.AI.Extensions.OpenAI;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using SampleApp;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";
// Create AI Project client to be used by chat client agents.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential());
// Demonstrates how to work with structured output via ResponseFormat with the non-generic RunAsync method.
// This approach is useful when:
// a. Structured output is used for inter-agent communication, where one agent produces structured output
// and passes it as text to another agent as input, without the need for the caller to directly work with the structured output.
// b. The type of the structured output is not known at compile time, so the generic RunAsync<T> method cannot be used.
// c. The type of the structured output is represented by JSON schema only, without a corresponding class or type in the code.
await UseStructuredOutputWithResponseFormatAsync(aiProjectClient, deploymentName);
// Demonstrates how to work with structured output via the generic RunAsync<T> method.
// This approach is useful when the caller needs to directly work with the structured output in the code
// via an instance of the corresponding class or type and the type is known at compile time.
await UseStructuredOutputWithRunAsync(aiProjectClient, deploymentName);
// Demonstrates how to work with structured output when streaming using the RunStreamingAsync method.
await UseStructuredOutputWithRunStreamingAsync(aiProjectClient, deploymentName);
// Demonstrates how to add structured output support to agents that don't natively support it using the structured output middleware.
// This approach is useful when working with agents that don't support structured output natively, or agents using models
// that don't have the capability to produce structured output, allowing you to still leverage structured output features by transforming
// the text output from the agent into structured data using a chat client.
await UseStructuredOutputWithMiddlewareAsync(aiProjectClient, deploymentName);
static async Task UseStructuredOutputWithResponseFormatAsync(AIProjectClient aiProjectClient, string deploymentName)
{
Console.WriteLine("=== Structured Output with ResponseFormat ===");
// Create the agent
AIAgent agent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions()
{
Name = "HelpfulAssistant",
ChatOptions = new()
{
ModelId = deploymentName,
Instructions = "You are a helpful assistant.",
// Specify CityInfo as the type parameter of ForJsonSchema to indicate the expected structured output from the agent.
ResponseFormat = ChatResponseFormat.ForJsonSchema<CityInfo>()
}
});
// Invoke the agent with some unstructured input to extract the structured information from.
AgentResponse response = await agent.RunAsync("Provide information about the capital of France.");
// Access the structured output via the Text property of the agent response as JSON in scenarios when JSON as text is required
// and no object instance is needed (e.g., for logging, forwarding to another service, or storing in a database).
Console.WriteLine("Assistant Output (JSON):");
Console.WriteLine(response.Text);
Console.WriteLine();
// Deserialize the JSON text to work with the structured object in scenarios when you need to access properties,
// perform operations, or pass the data to methods that require the typed object instance.
CityInfo cityInfo = JsonSerializer.Deserialize<CityInfo>(response.Text)!;
Console.WriteLine("Assistant Output (Deserialized):");
Console.WriteLine($"Name: {cityInfo.Name}");
Console.WriteLine();
}
static async Task UseStructuredOutputWithRunAsync(AIProjectClient aiProjectClient, string deploymentName)
{
Console.WriteLine("=== Structured Output with RunAsync<T> ===");
// Create the agent
AIAgent agent = aiProjectClient.AsAIAgent(deploymentName, name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
// Set CityInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke it with some unstructured input.
AgentResponse<CityInfo> response = await agent.RunAsync<CityInfo>("Provide information about the capital of France.");
// Access the structured output via the Result property of the agent response.
CityInfo cityInfo = response.Result;
Console.WriteLine("Assistant Output:");
Console.WriteLine($"Name: {cityInfo.Name}");
Console.WriteLine();
}
static async Task UseStructuredOutputWithRunStreamingAsync(AIProjectClient aiProjectClient, string deploymentName)
{
Console.WriteLine("=== Structured Output with RunStreamingAsync ===");
// Create the agent
AIAgent agent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions()
{
Name = "HelpfulAssistant",
ChatOptions = new()
{
ModelId = deploymentName,
Instructions = "You are a helpful assistant.",
// Specify CityInfo as the type parameter of ForJsonSchema to indicate the expected structured output from the agent.
ResponseFormat = ChatResponseFormat.ForJsonSchema<CityInfo>()
}
});
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
IAsyncEnumerable<AgentResponseUpdate> updates = agent.RunStreamingAsync("Provide information about the capital of France.");
// Assemble all the parts of the streamed output.
AgentResponse nonGenericResponse = await updates.ToAgentResponseAsync();
// Access the structured output by deserializing JSON in the Text property.
CityInfo cityInfo = JsonSerializer.Deserialize<CityInfo>(nonGenericResponse.Text)!;
Console.WriteLine("Assistant Output:");
Console.WriteLine($"Name: {cityInfo.Name}");
Console.WriteLine();
}
static async Task UseStructuredOutputWithMiddlewareAsync(AIProjectClient aiProjectClient, string deploymentName)
{
Console.WriteLine("=== Structured Output with UseStructuredOutput Middleware ===");
// Create chat client that will transform the agent text response into structured output.
IChatClient meaiChatClient = aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForModel(deploymentName).AsIChatClientWithStoredOutputDisabled(deploymentName);
// Create the agent
AIAgent agent = meaiChatClient.AsAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
// Add structured output middleware via UseStructuredOutput method to add structured output support to the agent.
// This middleware transforms the agent's text response into structured data using a chat client.
// Since our agent does support structured output natively, we will add a middleware that removes ResponseFormat
// from the AgentRunOptions to emulate an agent that doesn't support structured output natively
agent = agent
.AsBuilder()
.UseStructuredOutput(meaiChatClient)
.Use(ResponseFormatRemovalMiddleware, null)
.Build();
// Set CityInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke it with some unstructured input.
AgentResponse<CityInfo> response = await agent.RunAsync<CityInfo>("Provide information about the capital of France.");
// Access the structured output via the Result property of the agent response.
CityInfo cityInfo = response.Result;
Console.WriteLine("Assistant Output:");
Console.WriteLine($"Name: {cityInfo.Name}");
Console.WriteLine();
}
static Task<AgentResponse> ResponseFormatRemovalMiddleware(IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken)
{
// Remove any ResponseFormat from the options to emulate an agent that doesn't support structured output natively.
options = options?.Clone();
options?.ResponseFormat = null;
return innerAgent.RunAsync(messages, session, options, cancellationToken);
}
namespace SampleApp
{
/// <summary>
/// Represents information about a city, including its name.
/// </summary>
[Description("Information about a city")]
public sealed class CityInfo
{
[JsonPropertyName("name")]
public string? Name { get; set; }
}
}
@@ -0,0 +1,52 @@
# Structured Output with ChatClientAgent
This sample demonstrates how to configure ChatClientAgent to produce structured output in JSON format using various approaches.
## What this sample demonstrates
- **ResponseFormat approach**: Configuring agents with JSON schema response format via `ChatResponseFormat.ForJsonSchema<T>()` for inter-agent communication or when the type is not known at compile time
- **Generic RunAsync<T> method**: Using the generic `RunAsync<T>` method for structured output when the caller needs to work directly with typed objects
- **Structured output with Streaming**: Using `RunStreamingAsync` to stream responses while still obtaining structured output by assembling and deserializing the streamed content
- **StructuredOutput middleware**: Adding structured output support to agents that don't natively support it (like A2A agents or models without structured output capability) by transforming text output into structured data using a chat client
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource
**Note**: This sample uses Azure OpenAI models. For more information, see [how to deploy Azure OpenAI models with Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/deploy-models-openai).
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource and have the `Cognitive Services OpenAI Contributor` role. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
## Environment Variables
Set the following environment variables:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/" # Replace with your Azure OpenAI resource endpoint
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini" # Optional, defaults to gpt-5.4-mini
```
## Run the sample
Navigate to the sample directory and run:
```powershell
cd dotnet/samples/02-agents/Agents/Agent_Step02_StructuredOutput
dotnet run
```
## Expected behavior
The sample will demonstrate four different approaches to structured output:
1. **Structured Output with ResponseFormat**: Creates an agent with `ResponseFormat` set to `ForJsonSchema<CityInfo>()`, invokes it with unstructured input, and accesses the structured output via the `Text` property
2. **Structured Output with RunAsync<T>**: Creates an agent and uses the generic `RunAsync<CityInfo>()` method to get a typed `AgentResponse<CityInfo>` with the result accessible via the `Result` property
3. **Structured Output with RunStreamingAsync**: Creates an agent with JSON schema response format, streams the response using `RunStreamingAsync`, assembles the updates using `ToAgentResponseAsync()`, and deserializes the JSON text into a typed object
4. **Structured Output with StructuredOutput Middleware**: Uses the `UseStructuredOutput` method on `AIAgentBuilder` to add structured output support to agents that don't natively support it
Each approach will output information about the capital of France (Paris) in a structured format.
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace SampleApp;
/// <summary>
/// A delegating AI agent that converts text responses from an inner AI agent into structured output using a chat client.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="StructuredOutputAgent"/> wraps an inner agent and uses a chat client to transform
/// the inner agent's text response into a structured JSON format based on the specified response format.
/// </para>
/// <para>
/// This agent requires a <see cref="ChatResponseFormatJson"/> to be specified either through the
/// <see cref="AgentRunOptions.ResponseFormat"/> or the <see cref="StructuredOutputAgentOptions.ChatOptions"/>
/// provided during construction.
/// </para>
/// </remarks>
internal sealed class StructuredOutputAgent : DelegatingAIAgent
{
private readonly IChatClient _chatClient;
private readonly StructuredOutputAgentOptions? _agentOptions;
/// <summary>
/// Initializes a new instance of the <see cref="StructuredOutputAgent"/> class.
/// </summary>
/// <param name="innerAgent">The underlying agent that generates text responses to be converted to structured output.</param>
/// <param name="chatClient">The chat client used to transform text responses into structured JSON format.</param>
/// <param name="options">Optional configuration options for the structured output agent.</param>
public StructuredOutputAgent(AIAgent innerAgent, IChatClient chatClient, StructuredOutputAgentOptions? options = null)
: base(innerAgent)
{
this._chatClient = chatClient ?? throw new ArgumentNullException(nameof(chatClient));
this._agentOptions = options;
}
/// <inheritdoc />
protected override async Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
// Run the inner agent first, to get back the text response we want to convert.
var textResponse = await this.InnerAgent.RunAsync(messages, session, options, cancellationToken).ConfigureAwait(false);
// Invoke the chat client to transform the text output into structured data.
ChatResponse soResponse = await this._chatClient.GetResponseAsync(
messages: this.GetChatMessages(textResponse.Text),
options: this.GetChatOptions(options),
cancellationToken: cancellationToken).ConfigureAwait(false);
return new StructuredOutputAgentResponse(soResponse, textResponse);
}
private List<ChatMessage> GetChatMessages(string? textResponseText)
{
List<ChatMessage> chatMessages = [];
if (this._agentOptions?.ChatClientSystemMessage is not null)
{
chatMessages.Add(new ChatMessage(ChatRole.System, this._agentOptions.ChatClientSystemMessage));
}
chatMessages.Add(new ChatMessage(ChatRole.User, textResponseText));
return chatMessages;
}
private ChatOptions GetChatOptions(AgentRunOptions? options)
{
ChatResponseFormat responseFormat = options?.ResponseFormat
?? this._agentOptions?.ChatOptions?.ResponseFormat
?? throw new InvalidOperationException($"A response format of type '{nameof(ChatResponseFormatJson)}' must be specified, but none was specified.");
if (responseFormat is not ChatResponseFormatJson jsonResponseFormat)
{
throw new NotSupportedException($"A response format of type '{nameof(ChatResponseFormatJson)}' must be specified, but was '{responseFormat.GetType().Name}'.");
}
var chatOptions = this._agentOptions?.ChatOptions?.Clone() ?? new ChatOptions();
chatOptions.ResponseFormat = jsonResponseFormat;
return chatOptions;
}
}
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace SampleApp;
/// <summary>
/// Represents configuration options for a <see cref="StructuredOutputAgent"/>.
/// </summary>
#pragma warning disable CA1812 // Instantiated via AIAgentBuilderExtensions.UseStructuredOutput optionsFactory parameter
internal sealed class StructuredOutputAgentOptions
#pragma warning restore CA1812
{
/// <summary>
/// Gets or sets the system message to use when invoking the chat client for structured output conversion.
/// </summary>
public string? ChatClientSystemMessage { get; set; }
/// <summary>
/// Gets or sets the chat options to use for the structured output conversion by the chat client
/// used by the agent.
/// </summary>
/// <remarks>
/// This property is optional. The <see cref="ChatOptions.ResponseFormat"/> should be set to a
/// <see cref="ChatResponseFormatJson"/> instance to specify the expected JSON schema for the structured output.
/// Note that if <see cref="AgentRunOptions.ResponseFormat"/> is provided when running the agent,
/// it will take precedence and override the <see cref="ChatOptions.ResponseFormat"/> specified here.
/// </remarks>
public ChatOptions? ChatOptions { get; set; }
}
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace SampleApp;
/// <summary>
/// Represents an agent response that contains structured output and
/// the original agent response from which the structured output was generated.
/// </summary>
internal sealed class StructuredOutputAgentResponse : AgentResponse
{
/// <summary>
/// Initializes a new instance of the <see cref="StructuredOutputAgentResponse"/> class.
/// </summary>
/// <param name="chatResponse">The <see cref="ChatResponse"/> containing the structured output.</param>
/// <param name="agentResponse">The original <see cref="AgentResponse"/> from the inner agent.</param>
public StructuredOutputAgentResponse(ChatResponse chatResponse, AgentResponse agentResponse) : base(chatResponse)
{
this.OriginalResponse = agentResponse;
}
/// <summary>
/// Gets the original non-structured response from the inner agent used by chat client to produce the structured output.
/// </summary>
public AgentResponse OriginalResponse { get; }
}