diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index c75597230d..ea7898a16e 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -29,12 +29,7 @@
-
-
-
-
-
-
+
@@ -48,6 +43,11 @@
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs b/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs
new file mode 100644
index 0000000000..c49c081316
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.AI.Agents;
+
+namespace Steps;
+
+///
+/// Demonstrates how to use structured outputs with .
+///
+public sealed class Step06_ChatClientAgent_StructuredOutputs(ITestOutputHelper output) : AgentSample(output)
+{
+ ///
+ /// Demonstrates processing structured outputs using JSON schemas to extract information about a person.
+ ///
+ [Theory]
+ [InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
+ [InlineData(ChatClientProviders.AzureOpenAI)]
+ [InlineData(ChatClientProviders.OpenAIAssistant)]
+ [InlineData(ChatClientProviders.OpenAIChatCompletion)]
+ [InlineData(ChatClientProviders.OpenAIResponses)]
+ public async Task RunWithCustomSchema(ChatClientProviders provider)
+ {
+ var agentOptions = new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
+ agentOptions.ChatOptions = new()
+ {
+ ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
+ schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
+ schemaName: "PersonInfo",
+ schemaDescription: "Information about a person including their name, age, and occupation"
+ )
+ };
+
+ // Create the server-side agent Id when applicable (depending on the provider).
+ agentOptions.Id = await base.AgentCreateAsync(provider, agentOptions);
+
+ using var chatClient = base.GetChatClient(provider, agentOptions);
+
+ ChatClientAgent agent = new(chatClient, agentOptions);
+
+ var thread = agent.GetNewThread();
+
+ const string Prompt = "Please provide information about John Smith, who is a 35-year-old software engineer.";
+
+ var updates = agent.RunStreamingAsync(Prompt, thread);
+ var agentResponse = await updates.ToAgentRunResponseAsync();
+
+ var personInfo = agentResponse.Deserialize(JsonSerializerOptions.Web);
+
+ Console.WriteLine("Assistant Output:");
+ Console.WriteLine($"Name: {personInfo.Name}");
+ Console.WriteLine($"Age: {personInfo.Age}");
+ Console.WriteLine($"Occupation: {personInfo.Occupation}");
+
+ // Clean up the server-side agent after use when applicable (depending on the provider).
+ await base.AgentCleanUpAsync(provider, agent, thread);
+ }
+
+ ///
+ /// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
+ ///
+ public class PersonInfo
+ {
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("age")]
+ public int? Age { get; set; }
+
+ [JsonPropertyName("occupation")]
+ public string? Occupation { get; set; }
+ }
+}
diff --git a/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_UsingFileSearchTools.cs b/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs
similarity index 98%
rename from dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_UsingFileSearchTools.cs
rename to dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs
index 7cb8f78a1d..a4fcf2570e 100644
--- a/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_UsingFileSearchTools.cs
+++ b/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs
@@ -15,7 +15,7 @@ namespace Steps;
/// Demonstrates how to use with file search tools and file references.
/// Shows uploading files to different providers and using them with file search capabilities to retrieve and analyze information from documents.
///
-public sealed class Step04_ChatClientAgent_UsingFileSearchTools(ITestOutputHelper output) : AgentSample(output)
+public sealed class Step07_ChatClientAgent_UsingFileSearchTools(ITestOutputHelper output) : AgentSample(output)
{
[Theory]
[InlineData(ChatClientProviders.AzureAIAgentsPersistent)]
diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs
index e86a75d0c9..5507171516 100644
--- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs
+++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs
@@ -371,13 +371,35 @@ namespace Azure.AI.Agents.Persistent
{
if (options.ResponseFormat is ChatResponseFormatJson jsonFormat)
{
- runOptions.ResponseFormat = jsonFormat.Schema is { } schema ?
- BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(new Dictionary()
+ if (jsonFormat.Schema is JsonElement schema)
+ {
+ var schemaNode = JsonSerializer.SerializeToNode(schema, AgentsChatClientJsonContext.Default.JsonElement)!;
+
+ var jsonSchemaObject = new JsonObject
{
- ["type"] = "json_schema",
- ["json_schema"] = JsonSerializer.SerializeToNode(schema, AgentsChatClientJsonContext.Default.JsonNode),
- }, AgentsChatClientJsonContext.Default.JsonObject)) :
- BinaryData.FromString("""{ "type": "json_object" }""");
+ ["schema"] = schemaNode
+ };
+
+ if (jsonFormat.SchemaName is not null)
+ {
+ jsonSchemaObject["name"] = jsonFormat.SchemaName;
+ }
+ if (jsonFormat.SchemaDescription is not null)
+ {
+ jsonSchemaObject["description"] = jsonFormat.SchemaDescription;
+ }
+
+ runOptions.ResponseFormat =
+ BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(new()
+ {
+ ["type"] = "json_schema",
+ ["json_schema"] = jsonSchemaObject,
+ }, AgentsChatClientJsonContext.Default.JsonObject));
+ }
+ else
+ {
+ runOptions.ResponseFormat = BinaryData.FromString("""{ "type": "json_object" }""");
+ }
}
}
}
diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs
index 587f5adf5e..07ed61ceb0 100644
--- a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs
+++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs
@@ -879,7 +879,9 @@ public class ChatClientAgentThreadTests
public void VerifyJsonDeserialization_HandlesMalformedJson()
{
// Arrange - Invalid JSON structure
+#pragma warning disable JSON001 // Invalid JSON pattern
string invalidJson = "{ invalid json";
+#pragma warning restore JSON001 // Invalid JSON pattern
// Act & Assert
Assert.Throws(() => JsonSerializer.Deserialize(invalidJson));