From dd69cabc67139e1d17f7fabadaffd43fcf4ef6c0 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:40:39 +0000 Subject: [PATCH] .NET: Seal factory contexts and add non JSO deserialize overloads (#3066) * Seal factory contexts and add non JSO deserialize overloads * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../AgentRunResponse.cs | 18 +++++++ .../ChatClient/ChatClientAgentOptions.cs | 4 +- .../ChatClientAgentRunResponse{T}.cs | 1 - .../AgentRunResponseTests.cs | 48 +++++++++++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs index 001cfd9469..7828b5c62d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs @@ -291,6 +291,15 @@ public class AgentRunResponse return updates; } + /// + /// Deserializes the response text into the given type. + /// + /// The output type to deserialize into. + /// The result as the requested type. + /// The result is not parsable into the requested type. + public T Deserialize() => + this.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions); + /// /// Deserializes the response text into the given type using the specified serializer options. /// @@ -311,6 +320,15 @@ public class AgentRunResponse }; } + /// + /// Tries to deserialize response text into the given type. + /// + /// The output type to deserialize into. + /// The parsed structured output. + /// if parsing was successful; otherwise, . + public bool TryDeserialize([NotNullWhen(true)] out T? structuredOutput) => + this.TryDeserialize(AgentAbstractionsJsonUtilities.DefaultOptions, out structuredOutput); + /// /// Tries to deserialize response text into the given type using the specified serializer options. /// diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs index 4a72d66f2d..dd1ff3b228 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs @@ -80,7 +80,7 @@ public sealed class ChatClientAgentOptions /// /// Context object passed to the to create a new instance of . /// - public class AIContextProviderFactoryContext + public sealed class AIContextProviderFactoryContext { /// /// Gets or sets the serialized state of the , if any. @@ -97,7 +97,7 @@ public sealed class ChatClientAgentOptions /// /// Context object passed to the to create a new instance of . /// - public class ChatMessageStoreFactoryContext + public sealed class ChatMessageStoreFactoryContext { /// /// Gets or sets the serialized state of the chat message store, if any. diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentRunResponse{T}.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentRunResponse{T}.cs index 13b536a457..352be764eb 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentRunResponse{T}.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentRunResponse{T}.cs @@ -40,7 +40,6 @@ public sealed class ChatClientAgentRunResponse : AgentRunResponse /// /// /// If the response did not contain JSON, or if deserialization fails, this property will throw. - /// To avoid exceptions, use instead. /// public override T Result => this._response.Result; } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseTests.cs index 981f1e3933..a81446d062 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseTests.cs @@ -214,6 +214,12 @@ public class AgentRunResponseTests Assert.Equal(100, usageContent.Details.TotalTokenCount); } +#if NETFRAMEWORK + /// + /// Since Json Serialization using reflection is disabled in .net core builds, and we are using a custom type here that wouldn't + /// be registered with the default source generated serializer, this test will only pass in .net framework builds where reflection-based + /// serialization is available. + /// [Fact] public void ParseAsStructuredOutputSuccess() { @@ -221,6 +227,24 @@ public class AgentRunResponseTests var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger }; var response = new AgentRunResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal))); + // Act. + var animal = response.Deserialize(); + + // Assert. + Assert.NotNull(animal); + Assert.Equal(expectedResult.Id, animal.Id); + Assert.Equal(expectedResult.FullName, animal.FullName); + Assert.Equal(expectedResult.Species, animal.Species); + } +#endif + + [Fact] + public void ParseAsStructuredOutputWithJSOSuccess() + { + // Arrange. + var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger }; + var response = new AgentRunResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal))); + // Act. var animal = response.Deserialize(TestJsonSerializerContext.Default.Options); @@ -262,6 +286,12 @@ public class AgentRunResponseTests Assert.Throws(() => response.Deserialize(TestJsonSerializerContext.Default.Options)); } +#if NETFRAMEWORK + /// + /// Since Json Serialization using reflection is disabled in .net core builds, and we are using a custom type here that wouldn't + /// be registered with the default source generated serializer, this test will only pass in .net framework builds where reflection-based + /// serialization is available. + /// [Fact] public void TryParseAsStructuredOutputSuccess() { @@ -269,6 +299,24 @@ public class AgentRunResponseTests var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger }; var response = new AgentRunResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal))); + // Act. + response.TryDeserialize(out Animal? animal); + + // Assert. + Assert.NotNull(animal); + Assert.Equal(expectedResult.Id, animal.Id); + Assert.Equal(expectedResult.FullName, animal.FullName); + Assert.Equal(expectedResult.Species, animal.Species); + } +#endif + + [Fact] + public void TryParseAsStructuredOutputWithJSOSuccess() + { + // Arrange. + var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger }; + var response = new AgentRunResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal))); + // Act. response.TryDeserialize(TestJsonSerializerContext.Default.Options, out Animal? animal);