// Copyright (c) Microsoft. All rights reserved. using System; using System.Text.Json; using System.Threading.Tasks; using AgentConformance.IntegrationTests.Support; using Microsoft.Agents.AI; using Microsoft.Extensions.AI; namespace AgentConformance.IntegrationTests; /// /// Conformance tests for structured output handling for run methods on agents. /// /// The type of test fixture used by the concrete test implementation. /// Function to create the test fixture with. public abstract class StructuredOutputRunTests(Func createAgentFixture) : AgentTests(createAgentFixture) where TAgentFixture : IAgentFixture { [RetryFact(Constants.RetryCount, Constants.RetryDelay)] public virtual async Task RunWithResponseFormatReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; var session = await agent.CreateSessionAsync(); await using var cleanup = new SessionCleanup(session, this.Fixture); var options = new AgentRunOptions { ResponseFormat = ChatResponseFormat.ForJsonSchema(AgentAbstractionsJsonUtilities.DefaultOptions) }; // Act var response = await agent.RunAsync(new ChatMessage(ChatRole.User, "Provide information about the capital of France."), session, options); // Assert Assert.NotNull(response); Assert.Single(response.Messages); Assert.Contains("Paris", response.Text); Assert.True(TryDeserialize(response.Text, AgentAbstractionsJsonUtilities.DefaultOptions, out CityInfo cityInfo)); Assert.Equal("Paris", cityInfo.Name); } [RetryFact(Constants.RetryCount, Constants.RetryDelay)] public virtual async Task RunWithGenericTypeReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; var session = await agent.CreateSessionAsync(); await using var cleanup = new SessionCleanup(session, this.Fixture); // Act AgentResponse response = await agent.RunAsync( new ChatMessage(ChatRole.User, "Provide information about the capital of France."), session); // Assert Assert.NotNull(response); Assert.Single(response.Messages); Assert.Contains("Paris", response.Text); Assert.NotNull(response.Result); Assert.Equal("Paris", response.Result.Name); } [RetryFact(Constants.RetryCount, Constants.RetryDelay)] public virtual async Task RunWithPrimitiveTypeReturnsExpectedResultAsync() { // Arrange var agent = this.Fixture.Agent; var session = await agent.CreateSessionAsync(); await using var cleanup = new SessionCleanup(session, this.Fixture); // Act - Request a primitive type, which requires wrapping in an object schema AgentResponse response = await agent.RunAsync( new ChatMessage(ChatRole.User, "What is the sum of 15 and 27? Respond with just the number."), session); // Assert Assert.NotNull(response); Assert.Single(response.Messages); Assert.Equal(42, response.Result); } protected static bool TryDeserialize(string json, JsonSerializerOptions jsonSerializerOptions, out T structuredOutput) { try { T? deserialized = JsonSerializer.Deserialize(json, jsonSerializerOptions); if (deserialized is null) { structuredOutput = default!; return false; } structuredOutput = deserialized; return true; } catch { structuredOutput = default!; return false; } } } public sealed class CityInfo { public string? Name { get; set; } }