mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [Breaking] Structured Output improvements (#3761)
* .NET: Delete AgentResponse.{Try}Deserialize<T> methods (#3518)
* delete deserialize method of agent response
* order usings
* Update dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/SharedStateAgent.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/samples/AGUIClientServer/AGUIDojoServer/SharedState/SharedStateAgent.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* .NET:[Breaking] Add support for structured output (#3658)
* add support for so
* restore lost xml comment part
* fix using ordering
* Update dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgentStructuredOutput.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgentStructuredOutput.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_SO_WithFormatResponseTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* addressw pr review comments
* address pr review feedback
* address pr review comments
* fix compilation issues after the latest merge with main
* remove unnecessry options
* remove RunAsync<object> methods
* address code review feedback
* address pr review feedback
* make copy constructor protected
* address pr review feedback
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* .NET: Add decorator for structured output support (#3694)
* add decorator that adds structured output support to agents that don't natively support it.
* Update dotnet/src/Microsoft.Agents.AI/StructuredOutput/StructuredOutputAgentResponse.cs
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
* Update dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
* address pr review feedback
---------
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
* .NET: Support primitives and arrays for SO (#3696)
* wrap primitives and arrays
* fix file encoding
* address review comments
* add adr
* add missed change
* fix compilation issue
* address review comments
* rename adr file name
* reflect decision to have SO decorator as a reference implementation in samples
* .NET: Move SO agent to samples (#3820)
* move SO agent to samples
* change file encoding
* fix files encoding
* .NET: Preserve caller context (#3803)
* fix stuck orchestration
* add previously removed RunAsync<T> method to DurableAIAgent
* suppress IDE0005 warning
* update changelog and remove unused constructor of AgentResponse<T>
* updatge the changelog
* address PR review feedback
* .NET: Disable irrelevant integration test (#3913)
* disable irrelevant integration test
* Update dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentStructuredOutputRunTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* forgotten change
* address pr review feedback
* disable intermittently failing integration test.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
westey
parent
3168eb4870
commit
9506fb28f6
@@ -0,0 +1,110 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Conformance tests for structured output handling for run methods on agents.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAgentFixture">The type of test fixture used by the concrete test implementation.</typeparam>
|
||||
/// <param name="createAgentFixture">Function to create the test fixture with.</param>
|
||||
public abstract class StructuredOutputRunTests<TAgentFixture>(Func<TAgentFixture> createAgentFixture) : AgentTests<TAgentFixture>(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<CityInfo>(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<CityInfo> response = await agent.RunAsync<CityInfo>(
|
||||
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<int> response = await agent.RunAsync<int>(
|
||||
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<T>(string json, JsonSerializerOptions jsonSerializerOptions, out T structuredOutput)
|
||||
{
|
||||
try
|
||||
{
|
||||
T? deserialized = JsonSerializer.Deserialize<T>(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; }
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace AgentConformance.IntegrationTests.Support;
|
||||
|
||||
internal static class Constants
|
||||
public static class Constants
|
||||
{
|
||||
public const int RetryCount = 3;
|
||||
public const int RetryDelay = 5000;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace AgentConformance.IntegrationTests.Support;
|
||||
/// </summary>
|
||||
/// <param name="session">The session to delete.</param>
|
||||
/// <param name="fixture">The fixture that provides agent specific capabilities.</param>
|
||||
internal sealed class SessionCleanup(AgentSession session, IAgentFixture fixture) : IAsyncDisposable
|
||||
public sealed class SessionCleanup(AgentSession session, IAgentFixture fixture) : IAsyncDisposable
|
||||
{
|
||||
public async ValueTask DisposeAsync() =>
|
||||
await fixture.DeleteSessionAsync(session);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientAgentStructuredOutputRunTests() : StructuredOutputRunTests<AIProjectClientStructuredOutputFixture<CityInfo>>(() => new AIProjectClientStructuredOutputFixture<CityInfo>())
|
||||
{
|
||||
private const string NotSupported = "AIProjectClient does not support specifying structured output type at invocation time.";
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that response format provided at agent initialization is used when invoking RunAsync.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
|
||||
public async Task RunWithResponseFormatAtAgentInitializationReturnsExpectedResultAsync()
|
||||
{
|
||||
// Arrange
|
||||
var agent = this.Fixture.Agent;
|
||||
var session = await agent.CreateSessionAsync();
|
||||
await using var cleanup = new SessionCleanup(session, this.Fixture);
|
||||
|
||||
// Act
|
||||
var 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.True(TryDeserialize(response.Text, AgentAbstractionsJsonUtilities.DefaultOptions, out CityInfo cityInfo));
|
||||
Assert.Equal("Paris", cityInfo.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that generic RunAsync works with AIProjectClient when structured output is configured at agent initialization.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AIProjectClient does not support specifying the structured output type at invocation time yet.
|
||||
/// The type T provided to RunAsync<T> is ignored by AzureAIProjectChatClient and is only used
|
||||
/// for deserializing the agent response by AgentResponse<T>.Result.
|
||||
/// </remarks>
|
||||
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
|
||||
public async Task RunGenericWithResponseFormatAtAgentInitializationReturnsExpectedResultAsync()
|
||||
{
|
||||
// Arrange
|
||||
var agent = this.Fixture.Agent;
|
||||
var session = await agent.CreateSessionAsync();
|
||||
await using var cleanup = new SessionCleanup(session, this.Fixture);
|
||||
|
||||
// Act
|
||||
AgentResponse<CityInfo> response = await agent.RunAsync<CityInfo>(
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact(Skip = NotSupported)]
|
||||
public override Task RunWithGenericTypeReturnsExpectedResultAsync() =>
|
||||
base.RunWithGenericTypeReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = NotSupported)]
|
||||
public override Task RunWithResponseFormatReturnsExpectedResultAsync() =>
|
||||
base.RunWithResponseFormatReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = NotSupported)]
|
||||
public override Task RunWithPrimitiveTypeReturnsExpectedResultAsync() =>
|
||||
base.RunWithPrimitiveTypeReturnsExpectedResultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a fixture for testing AIProjectClient with structured output of type <typeparamref name="T"/> provided at agent initialization.
|
||||
/// </summary>
|
||||
public class AIProjectClientStructuredOutputFixture<T> : AIProjectClientFixture
|
||||
{
|
||||
public override Task InitializeAsync()
|
||||
{
|
||||
var agentOptions = new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new ChatOptions()
|
||||
{
|
||||
ResponseFormat = ChatResponseFormat.ForJsonSchema<T>(AgentAbstractionsJsonUtilities.DefaultOptions)
|
||||
},
|
||||
};
|
||||
|
||||
return this.InitializeAsync(agentOptions);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,13 @@ public class AIProjectClientFixture : IChatClientAgentFixture
|
||||
return await this._client.CreateAIAgentAsync(GenerateUniqueAgentName(name), model: s_config.DeploymentName, instructions: instructions, tools: aiTools);
|
||||
}
|
||||
|
||||
public async Task<ChatClientAgent> CreateChatClientAgentAsync(ChatClientAgentOptions options)
|
||||
{
|
||||
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
|
||||
|
||||
return await this._client.CreateAIAgentAsync(model: s_config.DeploymentName, options);
|
||||
}
|
||||
|
||||
public static string GenerateUniqueAgentName(string baseName) =>
|
||||
$"{baseName}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
|
||||
@@ -161,9 +168,15 @@ public class AIProjectClientFixture : IChatClientAgentFixture
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
this._client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
|
||||
this._agent = await this.CreateChatClientAgentAsync();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(ChatClientAgentOptions options)
|
||||
{
|
||||
this._client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
|
||||
this._agent = await this.CreateChatClientAgentAsync(options);
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace AzureAIAgentsPersistent.IntegrationTests;
|
||||
|
||||
public class AzureAIAgentsPersistentStructuredOutputRunTests() : StructuredOutputRunTests<AzureAIAgentsPersistentFixture>(() => new())
|
||||
{
|
||||
[Fact(Skip = "Fails intermittently, at build agent")]
|
||||
public override Task RunWithResponseFormatReturnsExpectedResultAsync() =>
|
||||
base.RunWithResponseFormatReturnsExpectedResultAsync();
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Abstractions.UnitTests.Models;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the structured output functionality in <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
public class AIAgentStructuredOutputTests
|
||||
{
|
||||
private readonly Mock<AIAgent> _agentMock;
|
||||
|
||||
public AIAgentStructuredOutputTests()
|
||||
{
|
||||
this._agentMock = new Mock<AIAgent> { CallBase = true };
|
||||
}
|
||||
|
||||
#region Schema Wrapping Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when requesting an object type, the schema is NOT wrapped.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_WithObjectType_DoesNotWrapSchemaAsync()
|
||||
{
|
||||
// Arrange
|
||||
Animal expectedAnimal = new() { Id = 1, FullName = "Test", Species = Species.Tiger };
|
||||
string responseJson = JsonSerializer.Serialize(expectedAnimal, TestJsonSerializerContext.Default.Animal);
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, responseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<Animal> result = await this._agentMock.Object.RunAsync<Animal>(
|
||||
"Get me an animal",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert - Verify the result is NOT marked as wrapped
|
||||
Assert.False(result.IsWrappedInObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when requesting a primitive type (int), the schema IS wrapped.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_WithPrimitiveType_WrapsSchemaAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":42}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<int> result = await this._agentMock.Object.RunAsync<int>(
|
||||
"Give me a number",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert - Verify the result is marked as wrapped
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when requesting an array type, the schema IS wrapped.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_WithArrayType_WrapsSchemaAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":[\"a\",\"b\",\"c\"]}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<string[]> result = await this._agentMock.Object.RunAsync<string[]>(
|
||||
"Give me an array of strings",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert - Verify the result is marked as wrapped
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when requesting an enum type, the schema IS wrapped.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_WithEnumType_WrapsSchemaAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":\"Tiger\"}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<Species> result = await this._agentMock.Object.RunAsync<Species>(
|
||||
"Give me a species",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert - Verify the result is marked as wrapped
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AgentResponse<T>.Result Unwrapping Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result correctly deserializes an object without unwrapping.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_DeserializesObjectWithoutUnwrapping()
|
||||
{
|
||||
// Arrange
|
||||
Animal expectedAnimal = new() { Id = 1, FullName = "Tigger", Species = Species.Tiger };
|
||||
string responseJson = JsonSerializer.Serialize(expectedAnimal, TestJsonSerializerContext.Default.Animal);
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, responseJson));
|
||||
AgentResponse<Animal> typedResponse = new(response, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Act
|
||||
Animal result = typedResponse.Result;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedAnimal.Id, result.Id);
|
||||
Assert.Equal(expectedAnimal.FullName, result.FullName);
|
||||
Assert.Equal(expectedAnimal.Species, result.Species);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result correctly unwraps and deserializes a primitive value.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_UnwrapsPrimitiveFromDataProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":42}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
AgentResponse<int> typedResponse = new(response, TestJsonSerializerContext.Default.Options) { IsWrappedInObject = true };
|
||||
|
||||
// Act
|
||||
int result = typedResponse.Result;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(42, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result correctly unwraps and deserializes an array.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_UnwrapsArrayFromDataProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":[\"apple\",\"banana\",\"cherry\"]}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
AgentResponse<string[]> typedResponse = new(response, TestJsonSerializerContext.Default.Options) { IsWrappedInObject = true };
|
||||
|
||||
// Act
|
||||
string[] result = typedResponse.Result;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(["apple", "banana", "cherry"], result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result correctly unwraps and deserializes an enum.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_UnwrapsEnumFromDataProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":\"Walrus\"}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
AgentResponse<Species> typedResponse = new(response, TestJsonSerializerContext.Default.Options) { IsWrappedInObject = true };
|
||||
|
||||
// Act
|
||||
Species result = typedResponse.Result;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Species.Walrus, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result falls back to original JSON when data property is missing.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_FallsBackWhenDataPropertyMissing()
|
||||
{
|
||||
// Arrange - simulate a case where wrapping was expected but response does not have data
|
||||
const string ResponseJson = "42";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
AgentResponse<int> typedResponse = new(response, TestJsonSerializerContext.Default.Options) { IsWrappedInObject = true };
|
||||
|
||||
// Act
|
||||
int result = typedResponse.Result;
|
||||
|
||||
// Assert - should still work by falling back to original JSON
|
||||
Assert.Equal(42, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result throws when response text is empty.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_ThrowsWhenTextIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, string.Empty));
|
||||
AgentResponse<int> typedResponse = new(response, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Act and Assert
|
||||
Assert.Throws<System.InvalidOperationException>(() => typedResponse.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AgentResponse{T}.Result throws when deserialized value is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AgentResponseGeneric_Result_ThrowsWhenDeserializedValueIsNull()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "null";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
AgentResponse<Animal> typedResponse = new(response, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Act and Assert
|
||||
Assert.Throws<System.InvalidOperationException>(() => typedResponse.Result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region End-to-End Tests
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end test: Request a primitive type, verify wrapping, and verify correct deserialization.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_PrimitiveEndToEnd_WrapsAndDeserializesCorrectlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":123}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<int> result = await this._agentMock.Object.RunAsync<int>(
|
||||
"Give me a number",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
Assert.Equal(123, result.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end test: Request an array type, verify wrapping, and verify correct deserialization.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_ArrayEndToEnd_WrapsAndDeserializesCorrectlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":[\"one\",\"two\",\"three\"]}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<string[]> result = await this._agentMock.Object.RunAsync<string[]>(
|
||||
"Give me an array of strings",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
Assert.Equal(["one", "two", "three"], result.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end test: Request an object type, verify no wrapping, and verify correct deserialization.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_ObjectEndToEnd_NoWrappingAndDeserializesCorrectlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
Animal expectedAnimal = new() { Id = 99, FullName = "Leo", Species = Species.Bear };
|
||||
string responseJson = JsonSerializer.Serialize(expectedAnimal, TestJsonSerializerContext.Default.Animal);
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, responseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<Animal> result = await this._agentMock.Object.RunAsync<Animal>(
|
||||
"Give me an animal",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.IsWrappedInObject);
|
||||
Assert.Equal(expectedAnimal.Id, result.Result.Id);
|
||||
Assert.Equal(expectedAnimal.FullName, result.Result.FullName);
|
||||
Assert.Equal(expectedAnimal.Species, result.Result.Species);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end test: Request an enum type, verify wrapping, and verify correct deserialization.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncGeneric_EnumEndToEnd_WrapsAndDeserializesCorrectlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string ResponseJson = "{\"data\":\"Bear\"}";
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, ResponseJson));
|
||||
|
||||
this._agentMock
|
||||
.Protected()
|
||||
.Setup<Task<AgentResponse>>("RunCoreAsync",
|
||||
ItExpr.IsAny<IEnumerable<ChatMessage>>(),
|
||||
ItExpr.IsAny<AgentSession?>(),
|
||||
ItExpr.IsAny<AgentRunOptions?>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(response);
|
||||
|
||||
// Act
|
||||
AgentResponse<Species> result = await this._agentMock.Object.RunAsync<Species>(
|
||||
"Give me a species",
|
||||
serializerOptions: TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsWrappedInObject);
|
||||
Assert.Equal(Species.Bear, result.Result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -214,30 +214,6 @@ public class AgentResponseTests
|
||||
Assert.Equal(100, usageContent.Details.TotalTokenCount);
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ParseAsStructuredOutputSuccess()
|
||||
{
|
||||
// Arrange.
|
||||
var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger };
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal)));
|
||||
|
||||
// Act.
|
||||
var animal = response.Deserialize<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 ParseAsStructuredOutputWithJSOSuccess()
|
||||
{
|
||||
@@ -246,7 +222,7 @@ public class AgentResponseTests
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal)));
|
||||
|
||||
// Act.
|
||||
var animal = response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options);
|
||||
var animal = JsonSerializer.Deserialize<Animal>(response.Text, TestJsonSerializerContext.Default.Options);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(animal);
|
||||
@@ -255,98 +231,6 @@ public class AgentResponseTests
|
||||
Assert.Equal(expectedResult.Species, animal.Species);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseAsStructuredOutputFailsWithEmptyString()
|
||||
{
|
||||
// Arrange.
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, string.Empty));
|
||||
|
||||
// Act & Assert.
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options));
|
||||
Assert.Equal("The response did not contain JSON to be deserialized.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseAsStructuredOutputFailsWithInvalidJson()
|
||||
{
|
||||
// Arrange.
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, "invalid json"));
|
||||
|
||||
// Act & Assert.
|
||||
Assert.Throws<JsonException>(() => response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseAsStructuredOutputFailsWithIncorrectTypedJson()
|
||||
{
|
||||
// Arrange.
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, "[]"));
|
||||
|
||||
// Act & Assert.
|
||||
Assert.Throws<JsonException>(() => response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options));
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryParseAsStructuredOutputSuccess()
|
||||
{
|
||||
// Arrange.
|
||||
var expectedResult = new Animal { Id = 1, FullName = "Tigger", Species = Species.Tiger };
|
||||
var response = new AgentResponse(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 AgentResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal)));
|
||||
|
||||
// Act.
|
||||
response.TryDeserialize(TestJsonSerializerContext.Default.Options, 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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParseAsStructuredOutputFailsWithEmptyText()
|
||||
{
|
||||
// Arrange.
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, string.Empty));
|
||||
|
||||
// Act & Assert.
|
||||
Assert.False(response.TryDeserialize<Animal>(TestJsonSerializerContext.Default.Options, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParseAsStructuredOutputFailsWithIncorrectTypedJson()
|
||||
{
|
||||
// Arrange.
|
||||
var response = new AgentResponse(new ChatMessage(ChatRole.Assistant, "[]"));
|
||||
|
||||
// Act & Assert.
|
||||
Assert.False(response.TryDeserialize<Animal>(TestJsonSerializerContext.Default.Options, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAgentResponseUpdatesWithNoMessagesProducesEmptyArray()
|
||||
{
|
||||
@@ -395,16 +279,4 @@ public class AgentResponseTests
|
||||
Assert.NotNull(update.AdditionalProperties);
|
||||
Assert.Equal("value", update.AdditionalProperties!["key"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_ThrowsWhenDeserializationReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, "null"));
|
||||
|
||||
// Act & Assert
|
||||
InvalidOperationException exception = Assert.Throws<InvalidOperationException>(
|
||||
() => response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options));
|
||||
Assert.Equal("The deserialized response is null.", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
@@ -27,7 +26,7 @@ public class AgentRunOptionsTests
|
||||
};
|
||||
|
||||
// Act
|
||||
var clone = new AgentRunOptions(options);
|
||||
var clone = options.Clone();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(clone);
|
||||
@@ -39,11 +38,6 @@ public class AgentRunOptionsTests
|
||||
Assert.Equal(42, clone.AdditionalProperties["key2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloningConstructorThrowsIfNull() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentRunOptions(null!));
|
||||
|
||||
[Fact]
|
||||
public void JsonSerializationRoundtrips()
|
||||
{
|
||||
@@ -77,4 +71,57 @@ public class AgentRunOptionsTests
|
||||
Assert.IsType<JsonElement>(value2);
|
||||
Assert.Equal(42, ((JsonElement)value2!).GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloneReturnsNewInstanceWithSameValues()
|
||||
{
|
||||
// Arrange
|
||||
var options = new AgentRunOptions
|
||||
{
|
||||
ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
|
||||
AllowBackgroundResponses = true,
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1",
|
||||
["key2"] = 42
|
||||
},
|
||||
ResponseFormat = ChatResponseFormat.Json
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRunOptions clone = options.Clone();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(clone);
|
||||
Assert.IsType<AgentRunOptions>(clone);
|
||||
Assert.NotSame(options, clone);
|
||||
Assert.Same(options.ContinuationToken, clone.ContinuationToken);
|
||||
Assert.Equal(options.AllowBackgroundResponses, clone.AllowBackgroundResponses);
|
||||
Assert.NotNull(clone.AdditionalProperties);
|
||||
Assert.NotSame(options.AdditionalProperties, clone.AdditionalProperties);
|
||||
Assert.Equal("value1", clone.AdditionalProperties["key1"]);
|
||||
Assert.Equal(42, clone.AdditionalProperties["key2"]);
|
||||
Assert.Same(options.ResponseFormat, clone.ResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloneCreatesIndependentAdditionalPropertiesDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var options = new AgentRunOptions
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRunOptions clone = options.Clone();
|
||||
clone.AdditionalProperties!["key2"] = "value2";
|
||||
|
||||
// Assert
|
||||
Assert.True(clone.AdditionalProperties.ContainsKey("key2"));
|
||||
Assert.False(options.AdditionalProperties.ContainsKey("key2"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
[JsonSerializable(typeof(AgentResponseUpdate))]
|
||||
[JsonSerializable(typeof(AgentRunOptions))]
|
||||
[JsonSerializable(typeof(Animal))]
|
||||
[JsonSerializable(typeof(Species))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
[JsonSerializable(typeof(Dictionary<string, object?>))]
|
||||
[JsonSerializable(typeof(string[]))]
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.DurableTask.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="DurableAgentRunOptions"/> class.
|
||||
/// </summary>
|
||||
public sealed class DurableAgentRunOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void CloneReturnsNewInstanceWithSameValues()
|
||||
{
|
||||
// Arrange
|
||||
DurableAgentRunOptions options = new()
|
||||
{
|
||||
EnableToolCalls = false,
|
||||
EnableToolNames = new List<string> { "tool1", "tool2" },
|
||||
IsFireAndForget = true,
|
||||
AllowBackgroundResponses = true,
|
||||
ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1",
|
||||
["key2"] = 42
|
||||
},
|
||||
ResponseFormat = ChatResponseFormat.Json
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRunOptions cloneAsBase = options.Clone();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(cloneAsBase);
|
||||
Assert.IsType<DurableAgentRunOptions>(cloneAsBase);
|
||||
DurableAgentRunOptions clone = (DurableAgentRunOptions)cloneAsBase;
|
||||
Assert.NotSame(options, clone);
|
||||
Assert.Equal(options.EnableToolCalls, clone.EnableToolCalls);
|
||||
Assert.NotNull(clone.EnableToolNames);
|
||||
Assert.NotSame(options.EnableToolNames, clone.EnableToolNames);
|
||||
Assert.Equal(2, clone.EnableToolNames.Count);
|
||||
Assert.Contains("tool1", clone.EnableToolNames);
|
||||
Assert.Contains("tool2", clone.EnableToolNames);
|
||||
Assert.Equal(options.IsFireAndForget, clone.IsFireAndForget);
|
||||
Assert.Equal(options.AllowBackgroundResponses, clone.AllowBackgroundResponses);
|
||||
Assert.Same(options.ContinuationToken, clone.ContinuationToken);
|
||||
Assert.NotNull(clone.AdditionalProperties);
|
||||
Assert.NotSame(options.AdditionalProperties, clone.AdditionalProperties);
|
||||
Assert.Equal("value1", clone.AdditionalProperties["key1"]);
|
||||
Assert.Equal(42, clone.AdditionalProperties["key2"]);
|
||||
Assert.Same(options.ResponseFormat, clone.ResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloneCreatesIndependentEnableToolNamesList()
|
||||
{
|
||||
// Arrange
|
||||
DurableAgentRunOptions options = new()
|
||||
{
|
||||
EnableToolNames = new List<string> { "tool1" }
|
||||
};
|
||||
|
||||
// Act
|
||||
DurableAgentRunOptions clone = (DurableAgentRunOptions)options.Clone();
|
||||
clone.EnableToolNames!.Add("tool2");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, clone.EnableToolNames.Count);
|
||||
Assert.Single(options.EnableToolNames);
|
||||
Assert.DoesNotContain("tool2", options.EnableToolNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloneCreatesIndependentAdditionalPropertiesDictionary()
|
||||
{
|
||||
// Arrange
|
||||
DurableAgentRunOptions options = new()
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
DurableAgentRunOptions clone = (DurableAgentRunOptions)options.Clone();
|
||||
clone.AdditionalProperties!["key2"] = "value2";
|
||||
|
||||
// Assert
|
||||
Assert.True(clone.AdditionalProperties.ContainsKey("key2"));
|
||||
Assert.False(options.AdditionalProperties.ContainsKey("key2"));
|
||||
}
|
||||
}
|
||||
+87
@@ -332,4 +332,91 @@ public class ChatClientAgentRunOptionsTests
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clone Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify that Clone returns a new instance with the same property values.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CloneReturnsNewInstanceWithSameValues()
|
||||
{
|
||||
// Arrange
|
||||
var chatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f };
|
||||
Func<IChatClient, IChatClient> factory = c => c;
|
||||
var runOptions = new ChatClientAgentRunOptions(chatOptions)
|
||||
{
|
||||
ChatClientFactory = factory,
|
||||
AllowBackgroundResponses = true,
|
||||
ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRunOptions cloneAsBase = runOptions.Clone();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(cloneAsBase);
|
||||
Assert.IsType<ChatClientAgentRunOptions>(cloneAsBase);
|
||||
ChatClientAgentRunOptions clone = (ChatClientAgentRunOptions)cloneAsBase;
|
||||
Assert.NotSame(runOptions, clone);
|
||||
Assert.NotNull(clone.ChatOptions);
|
||||
Assert.NotSame(runOptions.ChatOptions, clone.ChatOptions);
|
||||
Assert.Equal(100, clone.ChatOptions!.MaxOutputTokens);
|
||||
Assert.Equal(0.7f, clone.ChatOptions.Temperature);
|
||||
Assert.Same(factory, clone.ChatClientFactory);
|
||||
Assert.Equal(runOptions.AllowBackgroundResponses, clone.AllowBackgroundResponses);
|
||||
Assert.Same(runOptions.ContinuationToken, clone.ContinuationToken);
|
||||
Assert.NotNull(clone.AdditionalProperties);
|
||||
Assert.NotSame(runOptions.AdditionalProperties, clone.AdditionalProperties);
|
||||
Assert.Equal("value1", clone.AdditionalProperties["key1"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that modifying the cloned ChatOptions does not affect the original.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CloneCreatesIndependentChatOptions()
|
||||
{
|
||||
// Arrange
|
||||
var chatOptions = new ChatOptions { MaxOutputTokens = 100 };
|
||||
var runOptions = new ChatClientAgentRunOptions(chatOptions);
|
||||
|
||||
// Act
|
||||
ChatClientAgentRunOptions clone = (ChatClientAgentRunOptions)runOptions.Clone();
|
||||
clone.ChatOptions!.MaxOutputTokens = 200;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(100, runOptions.ChatOptions!.MaxOutputTokens);
|
||||
Assert.Equal(200, clone.ChatOptions.MaxOutputTokens);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that modifying the cloned AdditionalProperties does not affect the original.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void CloneCreatesIndependentAdditionalPropertiesDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var runOptions = new ChatClientAgentRunOptions
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
["key1"] = "value1"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
ChatClientAgentRunOptions clone = (ChatClientAgentRunOptions)runOptions.Clone();
|
||||
clone.AdditionalProperties!["key2"] = "value2";
|
||||
|
||||
// Assert
|
||||
Assert.True(clone.AdditionalProperties.ContainsKey("key2"));
|
||||
Assert.False(runOptions.AdditionalProperties.ContainsKey("key2"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -943,45 +942,6 @@ public partial class ChatClientAgentTests
|
||||
|
||||
#endregion
|
||||
|
||||
#region RunAsync Structured Output Tests
|
||||
|
||||
/// <summary>
|
||||
/// Verify the invocation of <see cref="ChatClientAgent"/> with specified type parameter is
|
||||
/// propagated to the underlying <see cref="IChatClient"/> call and the expected structured output is returned.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunAsyncWithTypeParameterInvokesChatClientMethodForStructuredOutputAsync()
|
||||
{
|
||||
// Arrange
|
||||
Animal expectedSO = new() { Id = 1, FullName = "Tigger", Species = Species.Tiger };
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedSO, JsonContext2.Default.Animal)))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new());
|
||||
|
||||
// Act
|
||||
AgentResponse<Animal> agentResponse = await agent.RunAsync<Animal>(messages: [new(ChatRole.User, "Hello")], serializerOptions: JsonContext2.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.Single(agentResponse.Messages);
|
||||
|
||||
Assert.NotNull(agentResponse.Result);
|
||||
Assert.Equal(expectedSO.Id, agentResponse.Result.Id);
|
||||
Assert.Equal(expectedSO.FullName, agentResponse.Result.FullName);
|
||||
Assert.Equal(expectedSO.Species, agentResponse.Result.Species);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property Override Tests
|
||||
|
||||
/// <summary>
|
||||
@@ -1999,20 +1959,6 @@ public partial class ChatClientAgentTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Animal
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
public Species Species { get; set; }
|
||||
}
|
||||
|
||||
private enum Species
|
||||
{
|
||||
Bear,
|
||||
Tiger,
|
||||
Walrus,
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||
[JsonSerializable(typeof(Animal))]
|
||||
private sealed partial class JsonContext2 : JsonSerializerContext;
|
||||
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
public partial class ChatClientAgent_StructuredOutput_WithFormatResponseTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync_ResponseFormatProvidedAtAgentInitialization_IsPropagatedToChatClientAsync()
|
||||
{
|
||||
// Arrange
|
||||
ChatResponseFormat? capturedResponseFormat = null;
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) => capturedResponseFormat = opts?.ResponseFormat)
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "test"))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatResponseFormatJson responseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new ChatOptions()
|
||||
{
|
||||
ResponseFormat = responseFormat
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
await agent.RunAsync(messages: [new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedResponseFormat);
|
||||
Assert.Same(responseFormat, capturedResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ResponseFormatProvidedAtAgentInvocation_IsPropagatedToChatClientAsync()
|
||||
{
|
||||
// Arrange
|
||||
ChatResponseFormat? capturedResponseFormat = null;
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) => capturedResponseFormat = opts?.ResponseFormat)
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "test"))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatResponseFormatJson responseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object);
|
||||
|
||||
ChatClientAgentRunOptions runOptions = new()
|
||||
{
|
||||
ResponseFormat = responseFormat
|
||||
};
|
||||
|
||||
// Act
|
||||
await agent.RunAsync(messages: [new(ChatRole.User, "Hello")], options: runOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedResponseFormat);
|
||||
Assert.Same(responseFormat, capturedResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ResponseFormatProvidedAtAgentInvocation_OverridesOneProvidedAtAgentInitializationAsync()
|
||||
{
|
||||
// Arrange
|
||||
ChatResponseFormat? capturedResponseFormat = null;
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) => capturedResponseFormat = opts?.ResponseFormat)
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "test"))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatResponseFormatJson initializationResponseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
ChatResponseFormatJson invocationResponseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new ChatOptions()
|
||||
{
|
||||
ResponseFormat = initializationResponseFormat
|
||||
},
|
||||
});
|
||||
|
||||
ChatClientAgentRunOptions runOptions = new()
|
||||
{
|
||||
ResponseFormat = invocationResponseFormat
|
||||
};
|
||||
|
||||
// Act
|
||||
await agent.RunAsync(messages: [new(ChatRole.User, "Hello")], options: runOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedResponseFormat);
|
||||
Assert.Same(invocationResponseFormat, capturedResponseFormat);
|
||||
Assert.NotSame(initializationResponseFormat, capturedResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ResponseFormatProvidedAtAgentRunOptions_OverridesOneProvidedViaChatOptionsAsync()
|
||||
{
|
||||
// Arrange
|
||||
ChatResponseFormat? capturedResponseFormat = null;
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) => capturedResponseFormat = opts?.ResponseFormat)
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "test"))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatResponseFormatJson chatOptionsResponseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
ChatResponseFormatJson runOptionsResponseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object);
|
||||
|
||||
ChatClientAgentRunOptions runOptions = new()
|
||||
{
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
ResponseFormat = chatOptionsResponseFormat
|
||||
},
|
||||
ResponseFormat = runOptionsResponseFormat
|
||||
};
|
||||
|
||||
// Act
|
||||
await agent.RunAsync(messages: [new(ChatRole.User, "Hello")], options: runOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedResponseFormat);
|
||||
Assert.Same(runOptionsResponseFormat, capturedResponseFormat);
|
||||
Assert.NotSame(chatOptionsResponseFormat, capturedResponseFormat);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_StructuredOutputResponse_IsAvailableAsTextOnAgentResponseAsync()
|
||||
{
|
||||
// Arrange
|
||||
Animal expectedAnimal = new() { FullName = "Wally the Walrus", Id = 1, Species = Species.Walrus };
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedAnimal, JsonContext4.Default.Animal)))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatResponseFormatJson responseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext4.Default.Options);
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new ChatClientAgentOptions
|
||||
{
|
||||
ChatOptions = new ChatOptions()
|
||||
{
|
||||
ResponseFormat = responseFormat
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
AgentResponse agentResponse = await agent.RunAsync(messages: [new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(agentResponse?.Text);
|
||||
|
||||
Animal? deserialised = JsonSerializer.Deserialize(agentResponse.Text, JsonContext4.Default.Animal);
|
||||
Assert.NotNull(deserialised);
|
||||
Assert.Equal(expectedAnimal.Id, deserialised.Id);
|
||||
Assert.Equal(expectedAnimal.FullName, deserialised.FullName);
|
||||
Assert.Equal(expectedAnimal.Species, deserialised.Species);
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(Animal))]
|
||||
private sealed partial class JsonContext4 : JsonSerializerContext;
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
public partial class ChatClientAgent_StructuredOutput_WithRunAsyncTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync_WithGenericType_SetsJsonSchemaResponseFormatAndDeserializesResultAsync()
|
||||
{
|
||||
// Arrange
|
||||
ChatResponseFormat? capturedResponseFormat = null;
|
||||
ChatResponseFormatJson expectedResponseFormat = ChatResponseFormat.ForJsonSchema<Animal>(JsonContext3.Default.Options);
|
||||
Animal expectedSO = new() { Id = 1, FullName = "Tigger", Species = Species.Tiger };
|
||||
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService.Setup(s => s
|
||||
.GetResponseAsync(
|
||||
It.IsAny<IEnumerable<ChatMessage>>(),
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) => capturedResponseFormat = opts?.ResponseFormat)
|
||||
.ReturnsAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedSO, JsonContext3.Default.Animal)))
|
||||
{
|
||||
ResponseId = "test",
|
||||
});
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object);
|
||||
|
||||
// Act
|
||||
AgentResponse<Animal> agentResponse = await agent.RunAsync<Animal>(
|
||||
messages: [new(ChatRole.User, "Hello")],
|
||||
serializerOptions: JsonContext3.Default.Options);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedResponseFormat);
|
||||
Assert.Equal(expectedResponseFormat.Schema?.GetRawText(), ((ChatResponseFormatJson)capturedResponseFormat).Schema?.GetRawText());
|
||||
|
||||
Animal animal = agentResponse.Result;
|
||||
Assert.NotNull(animal);
|
||||
Assert.Equal(expectedSO.Id, animal.Id);
|
||||
Assert.Equal(expectedSO.FullName, animal.FullName);
|
||||
Assert.Equal(expectedSO.Species, animal.Species);
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||
[JsonSerializable(typeof(Animal))]
|
||||
private sealed partial class JsonContext3 : JsonSerializerContext;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
internal sealed class Animal
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
public Species Species { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
internal enum Species
|
||||
{
|
||||
Bear,
|
||||
Tiger,
|
||||
Walrus,
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIAssistant.IntegrationTests;
|
||||
|
||||
public class OpenAIAssistantStructuredOutputRunTests() : StructuredOutputRunTests<OpenAIAssistantFixture>(() => new())
|
||||
{
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace OpenAIChatCompletion.IntegrationTests;
|
||||
|
||||
public class OpenAIChatCompletionStructuredOutputRunTests() : StructuredOutputRunTests<OpenAIChatCompletionFixture>(() => new(useReasoningChatModel: false))
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace ResponseResult.IntegrationTests;
|
||||
|
||||
public class OpenAIResponseStructuredOutputRunTests() : StructuredOutputRunTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user