Merge branch 'main' into feature-xunit3-mtp-upgrade

This commit is contained in:
westey
2026-03-04 14:28:24 +00:00
committed by GitHub
Unverified
89 changed files with 3497 additions and 755 deletions
+5 -5
View File
@@ -19,8 +19,8 @@
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0" />
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-beta.5" />
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-beta.5" />
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0-beta.1" />
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="2.0.0-beta.1" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.8" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
@@ -35,7 +35,7 @@
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.3" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.ClientModel" Version="1.9.0" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
@@ -94,7 +94,7 @@
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.67.0-preview" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.67.0" />
<!-- Agent SDKs -->
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.23" />
<PackageVersion Include="GitHub.Copilot.SDK" Version="0.1.29" />
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
<!-- M365 Agents SDK -->
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
@@ -185,4 +185,4 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -89,6 +89,7 @@ namespace SampleApp
internal sealed class UserInfoMemory : AIContextProvider
{
private readonly ProviderSessionState<UserInfo> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly IChatClient _chatClient;
public UserInfoMemory(IChatClient chatClient, Func<AgentSession?, UserInfo>? stateInitializer = null)
@@ -99,7 +100,7 @@ namespace SampleApp
this._chatClient = chatClient;
}
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
public UserInfo GetUserInfo(AgentSession session)
=> this._sessionState.GetOrInitializeState(session);
@@ -79,6 +79,7 @@ namespace SampleApp
internal sealed class VectorChatHistoryProvider : ChatHistoryProvider
{
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly VectorStore _vectorStore;
public VectorChatHistoryProvider(
@@ -92,7 +93,7 @@ namespace SampleApp
this._vectorStore = vectorStore ?? throw new ArgumentNullException(nameof(vectorStore));
}
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
public string GetSessionDbKey(AgentSession session)
=> this._sessionState.GetOrInitializeState(session).SessionDbKey;
@@ -60,7 +60,7 @@ Console.WriteLine();
// Submit the red team run to the service
Console.WriteLine("Submitting red team run...");
RedTeam redTeamRun = await aiProjectClient.RedTeams.CreateAsync(redTeamConfig);
RedTeam redTeamRun = await aiProjectClient.RedTeams.CreateAsync(redTeamConfig, options: null);
Console.WriteLine($"Red team run created: {redTeamRun.Name}");
Console.WriteLine($"Status: {redTeamRun.Status}");
@@ -35,7 +35,7 @@ string userScope = $"user_{Environment.MachineName}";
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
// Create the Memory Search tool configuration
MemorySearchTool memorySearchTool = new(memoryStoreName, userScope)
MemorySearchPreviewTool memorySearchTool = new(memoryStoreName, userScope)
{
// Optional: Configure how quickly new memories are indexed (in seconds)
UpdateDelay = 1,
@@ -88,7 +88,9 @@ internal sealed class Program
{
string workflowYaml = File.ReadAllText("MathChat.yaml");
#pragma warning disable AAIP001 // WorkflowAgentDefinition is experimental
WorkflowAgentDefinition workflowAgentDefinition = WorkflowAgentDefinition.FromYaml(workflowYaml);
#pragma warning restore AAIP001
return
await agentClient.CreateAgentAsync(
@@ -36,6 +36,8 @@ public abstract class AIContextProvider
private static IEnumerable<ChatMessage> DefaultNoopFilter(IEnumerable<ChatMessage> messages)
=> messages;
private IReadOnlyList<string>? _stateKeys;
/// <summary>
/// Initializes a new instance of the <see cref="AIContextProvider"/> class.
/// </summary>
@@ -68,14 +70,15 @@ public abstract class AIContextProvider
protected Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> StoreInputResponseMessageFilter { get; }
/// <summary>
/// Gets the key used to store the provider state in the <see cref="AgentSession.StateBag"/>.
/// Gets the set of keys used to store the provider state in the <see cref="AgentSession.StateBag"/>.
/// </summary>
/// <remarks>
/// The default value is the name of the concrete type (e.g. <c>"TextSearchProvider"</c>).
/// Implementations may override this to provide a custom key, for example when multiple
/// instances of the same provider type are used in the same session.
/// The default value is a single-element set containing the name of the concrete type (e.g. <c>"TextSearchProvider"</c>).
/// Implementations may override this to provide custom keys, for example when multiple
/// instances of the same provider type are used in the same session, or when a provider
/// stores state under more than one key.
/// </remarks>
public virtual string StateKey => this.GetType().Name;
public virtual IReadOnlyList<string> StateKeys => this._stateKeys ??= [this.GetType().Name];
/// <summary>
/// Called at the start of agent invocation to provide additional context.
@@ -45,6 +45,7 @@ public abstract class ChatHistoryProvider
private static IEnumerable<ChatMessage> DefaultNoopFilter(IEnumerable<ChatMessage> messages)
=> messages;
private IReadOnlyList<string>? _stateKeys;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>>? _provideOutputMessageFilter;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> _storeInputRequestMessageFilter;
private readonly Func<IEnumerable<ChatMessage>, IEnumerable<ChatMessage>> _storeInputResponseMessageFilter;
@@ -66,14 +67,15 @@ public abstract class ChatHistoryProvider
}
/// <summary>
/// Gets the key used to store the provider state in the <see cref="AgentSession.StateBag"/>.
/// Gets the set of keys used to store the provider state in the <see cref="AgentSession.StateBag"/>.
/// </summary>
/// <remarks>
/// The default value is the name of the concrete type (e.g. <c>"InMemoryChatHistoryProvider"</c>).
/// Implementations may override this to provide a custom key, for example when multiple
/// instances of the same provider type are used in the same session.
/// The default value is a single-element set containing the name of the concrete type (e.g. <c>"InMemoryChatHistoryProvider"</c>).
/// Implementations may override this to provide custom keys, for example when multiple
/// instances of the same provider type are used in the same session, or when a provider
/// stores state under more than one key.
/// </remarks>
public virtual string StateKey => this.GetType().Name;
public virtual IReadOnlyList<string> StateKeys => this._stateKeys ??= [this.GetType().Name];
/// <summary>
/// Called at the start of agent invocation to provide messages for the next agent invocation.
@@ -27,6 +27,7 @@ namespace Microsoft.Agents.AI;
public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
{
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
/// <summary>
/// Initializes a new instance of the <see cref="InMemoryChatHistoryProvider"/> class.
@@ -50,7 +51,7 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
/// <summary>
/// Gets the chat reducer used to process or reduce chat messages. If null, no reduction logic will be applied.
@@ -39,7 +39,7 @@ public static partial class AzureAIProjectChatClientExtensions
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
/// <remarks>
/// When instantiating a <see cref="ChatClientAgent"/> by using an <see cref="AgentReference"/>, minimal information will be available about the agent in the instance level, and any logic that relies
/// on <see cref="AIAgent.GetService(Type, object?)"/> to retrieve information about the agent like <see cref="AgentVersion" /> will receive <see langword="null"/> as the result.
/// on <see cref="AIAgent.GetService{TService}(object?)"/> to retrieve information about the agent like <see cref="AgentVersion" /> will receive <see langword="null"/> as the result.
/// </remarks>
public static ChatClientAgent AsAIAgent(
this AIProjectClient aiProjectClient,
@@ -355,28 +355,27 @@ public static partial class AzureAIProjectChatClientExtensions
private static readonly ModelReaderWriterOptions s_modelWriterOptionsWire = new("W");
/// <summary>
/// Asynchronously retrieves an agent record by name using the Protocol method with user-agent header.
/// Asynchronously retrieves an agent record by name using the protocol method to inject user-agent headers.
/// </summary>
private static async Task<AgentRecord> GetAgentRecordByNameAsync(AIProjectClient aiProjectClient, string agentName, CancellationToken cancellationToken)
{
ClientResult protocolResponse = await aiProjectClient.Agents.GetAgentAsync(agentName, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
var rawResponse = protocolResponse.GetRawResponse();
AgentRecord? result = ModelReaderWriter.Read<AgentRecord>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default);
return ClientResult.FromOptionalValue(result, rawResponse).Value!
?? throw new InvalidOperationException($"Agent with name '{agentName}' not found.");
return result ?? throw new InvalidOperationException($"Agent with name '{agentName}' not found.");
}
/// <summary>
/// Asynchronously creates an agent version using the Protocol method with user-agent header.
/// Asynchronously creates an agent version using the protocol method to inject user-agent headers.
/// </summary>
private static async Task<AgentVersion> CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, CancellationToken cancellationToken)
{
using BinaryContent protocolRequest = BinaryContent.Create(ModelReaderWriter.Write(creationOptions, ModelReaderWriterOptions.Json, AzureAIProjectsContext.Default));
ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, protocolRequest, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
BinaryData serializedOptions = ModelReaderWriter.Write(creationOptions, s_modelWriterOptionsWire, AzureAIProjectsContext.Default);
BinaryContent content = BinaryContent.Create(serializedOptions);
ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, content, foundryFeatures: null, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
var rawResponse = protocolResponse.GetRawResponse();
AgentVersion? result = ModelReaderWriter.Read<AgentVersion>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsOpenAIContext.Default);
return ClientResult.FromValue(result, rawResponse).Value!;
return result ?? throw new InvalidOperationException($"Failed to create agent version for agent '{agentName}'.");
}
private static async Task<ChatClientAgent> CreateAIAgentAsync(
@@ -22,6 +22,7 @@ namespace Microsoft.Agents.AI;
public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
{
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly CosmosClient _cosmosClient;
private readonly Container _container;
private readonly bool _ownsClient;
@@ -114,7 +115,7 @@ public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
/// <summary>
/// Initializes a new instance of the <see cref="CosmosChatHistoryProvider"/> class using a connection string.
@@ -32,6 +32,7 @@ public sealed class FoundryMemoryProvider : AIContextProvider
private const string DefaultContextPrompt = "## Memories\nConsider the following memories when answering user questions:";
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly string _contextPrompt;
private readonly string _memoryStoreName;
private readonly int _maxMemories;
@@ -82,7 +83,7 @@ public sealed class FoundryMemoryProvider : AIContextProvider
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
private static Func<AgentSession?, State> ValidateStateInitializer(Func<AgentSession?, State> stateInitializer) =>
session =>
@@ -27,6 +27,7 @@ public sealed class Mem0Provider : MessageAIContextProvider
private const string DefaultContextPrompt = "## Memories\nConsider the following memories when answering user questions:";
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly string _contextPrompt;
private readonly bool _enableSensitiveTelemetryData;
@@ -72,7 +73,7 @@ public sealed class Mem0Provider : MessageAIContextProvider
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
private static Func<AgentSession?, State> ValidateStateInitializer(Func<AgentSession?, State> stateInitializer) =>
session =>
@@ -12,6 +12,7 @@ namespace Microsoft.Agents.AI.Workflows;
internal sealed class WorkflowChatHistoryProvider : ChatHistoryProvider
{
private readonly ProviderSessionState<StoreState> _sessionState;
private IReadOnlyList<string>? _stateKeys;
/// <summary>
/// Initializes a new instance of the <see cref="WorkflowChatHistoryProvider"/> class.
@@ -30,7 +31,7 @@ internal sealed class WorkflowChatHistoryProvider : ChatHistoryProvider
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
internal sealed class StoreState
{
@@ -112,7 +112,7 @@ public sealed partial class ChatClientAgent : AIAgent
this.ChatHistoryProvider = options?.ChatHistoryProvider ?? new InMemoryChatHistoryProvider();
this.AIContextProviders = this._agentOptions?.AIContextProviders as IReadOnlyList<AIContextProvider> ?? this._agentOptions?.AIContextProviders?.ToList();
// Validate that no two providers share the same StateKey, since they would overwrite each other's state in the session.
// Validate that no two providers share any StateKeys, since they would overwrite each other's state in the session.
this._aiContextProviderStateKeys = ValidateAndCollectStateKeys(this._agentOptions?.AIContextProviders, this.ChatHistoryProvider);
this._logger = (loggerFactory ?? chatClient.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance).CreateLogger<ChatClientAgent>();
@@ -824,11 +824,17 @@ public sealed partial class ChatClientAgent : AIAgent
$"Only {nameof(ChatClientAgentSession.ConversationId)} or {nameof(this.ChatHistoryProvider)} may be used, but not both. The current {nameof(ChatClientAgentSession)} has a {nameof(ChatClientAgentSession.ConversationId)} indicating server-side chat history management, but an override {nameof(this.ChatHistoryProvider)} was provided via {nameof(AgentRunOptions.AdditionalProperties)}.");
}
// Validate that the override provider's StateKey does not clash with any AIContextProvider's StateKey.
if (overrideProvider is not null && this._aiContextProviderStateKeys.Contains(overrideProvider.StateKey))
// Validate that the override provider's StateKeys do not clash with any AIContextProvider's StateKeys.
if (overrideProvider is not null)
{
throw new InvalidOperationException(
$"The ChatHistoryProvider '{overrideProvider.GetType().Name}' uses the state key '{overrideProvider.StateKey}' which is already used by one of the configured AIContextProviders. Each provider must use a unique state key to avoid overwriting each other's state.");
foreach (var key in overrideProvider.StateKeys)
{
if (this._aiContextProviderStateKeys.Contains(key))
{
throw new InvalidOperationException(
$"The ChatHistoryProvider '{overrideProvider.GetType().Name}' uses state key '{key}' which is already used by one of the configured AIContextProviders. Each provider must use unique state keys to avoid overwriting each other's state.");
}
}
}
provider = overrideProvider;
@@ -879,7 +885,7 @@ public sealed partial class ChatClientAgent : AIAgent
private string GetLoggingAgentName() => this.Name ?? "UnnamedAgent";
/// <summary>
/// Validates that all configured providers have unique <see cref="AIContextProvider.StateKey"/> values
/// Validates that all configured providers have unique <see cref="AIContextProvider.StateKeys"/> values
/// and returns a <see cref="HashSet{T}"/> of the AIContextProvider state keys.
/// </summary>
private static HashSet<string> ValidateAndCollectStateKeys(IEnumerable<AIContextProvider>? aiContextProviders, ChatHistoryProvider? chatHistoryProvider)
@@ -890,10 +896,13 @@ public sealed partial class ChatClientAgent : AIAgent
{
foreach (var provider in aiContextProviders)
{
if (!stateKeys.Add(provider.StateKey))
foreach (var key in provider.StateKeys)
{
throw new InvalidOperationException(
$"Multiple providers use the same state key '{provider.StateKey}'. Each provider must use a unique state key to avoid overwriting each other's state.");
if (!stateKeys.Add(key))
{
throw new InvalidOperationException(
$"Multiple providers use the same state key '{key}'. Each provider must use a unique state key to avoid overwriting each other's state.");
}
}
}
}
@@ -905,11 +914,16 @@ public sealed partial class ChatClientAgent : AIAgent
$"The default {nameof(InMemoryChatHistoryProvider)} uses the state key '{nameof(InMemoryChatHistoryProvider)}', which is already used by one of the configured AIContextProviders. Each provider must use a unique state key to avoid overwriting each other's state. To resolve this, either configure a different state key for the AIContextProvider that is using '{nameof(InMemoryChatHistoryProvider)}' as its state key, or provide a custom ChatHistoryProvider with a unique state key.");
}
if (chatHistoryProvider is not null
&& stateKeys.Contains(chatHistoryProvider.StateKey))
if (chatHistoryProvider is not null)
{
throw new InvalidOperationException(
$"The ChatHistoryProvider '{chatHistoryProvider.GetType().Name}' uses the state key '{chatHistoryProvider.StateKey}' which is already used by one of the configured AIContextProviders. Each provider must use a unique state key to avoid overwriting each other's state. To resolve this, either configure a different state key for the AIContextProvider that is using '{chatHistoryProvider.StateKey}' as its state key, or reconfigure the custom ChatHistoryProvider with a unique state key.");
foreach (var key in chatHistoryProvider.StateKeys)
{
if (stateKeys.Contains(key))
{
throw new InvalidOperationException(
$"The ChatHistoryProvider '{chatHistoryProvider.GetType().Name}' uses state key '{key}' which is already used by one of the configured AIContextProviders. Each provider must use unique state keys to avoid overwriting each other's state. To resolve this, either configure different state keys for the AIContextProvider that shares keys with the ChatHistoryProvider, or reconfigure the custom ChatHistoryProvider with unique state keys.");
}
}
}
return stateKeys;
@@ -54,6 +54,7 @@ public sealed class ChatHistoryMemoryProvider : MessageAIContextProvider, IDispo
private const string ContentEmbeddingField = "ContentEmbedding";
private readonly ProviderSessionState<State> _sessionState;
private IReadOnlyList<string>? _stateKeys;
#pragma warning disable CA2213 // VectorStore is not owned by this class - caller is responsible for disposal
private readonly VectorStore _vectorStore;
@@ -128,7 +129,7 @@ public sealed class ChatHistoryMemoryProvider : MessageAIContextProvider, IDispo
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
/// <inheritdoc />
protected override async ValueTask<AIContext> ProvideAIContextAsync(AIContextProvider.InvokingContext context, CancellationToken cancellationToken = default)
@@ -40,6 +40,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
private const string DefaultCitationsPrompt = "Include citations to the source document with document name and link if document name and link is available.";
private readonly ProviderSessionState<TextSearchProviderState> _sessionState;
private IReadOnlyList<string>? _stateKeys;
private readonly Func<string, CancellationToken, Task<IEnumerable<TextSearchResult>>> _searchAsync;
private readonly ILogger<TextSearchProvider>? _logger;
private readonly AITool[] _tools;
@@ -88,7 +89,7 @@ public sealed class TextSearchProvider : MessageAIContextProvider
}
/// <inheritdoc />
public override string StateKey => this._sessionState.StateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys ??= [this._sessionState.StateKey];
/// <inheritdoc />
protected override async ValueTask<AIContext> ProvideAIContextAsync(AIContextProvider.InvokingContext context, CancellationToken cancellationToken = default)
@@ -132,10 +132,15 @@ public class AzureAIAgentsPersistentCreateTests
}
}
[Theory]
[InlineData("CreateWithChatClientAgentOptionsAsync")]
[InlineData("CreateWithFoundryOptionsAsync")]
public async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync(string createMechanism)
[Fact]
public Task CreateAgent_CreatesAgentWithCodeInterpreter_ChatClientAgentOptionsAsync()
=> this.CreateAgent_CreatesAgentWithCodeInterpreterAsync("CreateWithChatClientAgentOptionsAsync");
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
public Task CreateAgent_CreatesAgentWithCodeInterpreter_FoundryOptionsAsync()
=> this.CreateAgent_CreatesAgentWithCodeInterpreterAsync("CreateWithFoundryOptionsAsync");
private async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync(string createMechanism)
{
// Arrange.
const string AgentInstructions = """
@@ -43,23 +43,25 @@ public class InMemoryChatHistoryProviderTests
}
[Fact]
public void StateKey_ReturnsDefaultKey_WhenNoOptionsProvided()
public void StateKeys_ReturnsDefaultKey_WhenNoOptionsProvided()
{
// Arrange & Act
var provider = new InMemoryChatHistoryProvider();
// Assert
Assert.Equal("InMemoryChatHistoryProvider", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("InMemoryChatHistoryProvider", provider.StateKeys);
}
[Fact]
public void StateKey_ReturnsCustomKey_WhenSetViaOptions()
public void StateKeys_ReturnsCustomKey_WhenSetViaOptions()
{
// Arrange & Act
var provider = new InMemoryChatHistoryProvider(new() { StateKey = "custom-key" });
// Assert
Assert.Equal("custom-key", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("custom-key", provider.StateKeys);
}
[Fact]
@@ -467,7 +467,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Test instructions");
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -475,7 +475,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
var agent = await client.CreateAIAgentAsync("test-model", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -490,7 +490,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectlyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Test instructions");
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -499,7 +499,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
TestChatClient? testChatClient = null;
// Act
var agent = await client.CreateAIAgentAsync(
var agent = await testClient.Client.CreateAIAgentAsync(
"test-model",
options,
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
@@ -560,12 +560,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithDefinition_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -582,12 +582,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -602,12 +602,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
{
// Arrange
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definition);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definition);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -628,12 +628,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
// Create a response definition with the same tool
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -667,12 +667,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
definitionResponse.Tools.Add(tool);
}
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -803,10 +803,10 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definitionResponse = GeneratePromptDefinitionResponse(new PromptAgentDefinition("test-model") { Instructions = "Test instructions" }, tools);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
// Act
var agent = await client.CreateAIAgentAsync(
var agent = await testClient.Client.CreateAIAgentAsync(
"test-agent",
"test-model",
"Test instructions",
@@ -831,14 +831,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithDefinitionTools_CreatesAgentAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
definition.Tools.Add(ResponseTool.CreateFunctionTool("async_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -885,7 +885,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var sharepointOptions = new SharePointGroundingToolOptions();
sharepointOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
var structuredOutputs = new StructuredOutputDefinition("name", "description", BinaryData.FromString(AIJsonUtilities.CreateJsonSchema(new { id = "test" }.GetType()).ToString()), false);
var structuredOutputs = new StructuredOutputDefinition("name", "description", new Dictionary<string, BinaryData> { ["schema"] = BinaryData.FromString(AIJsonUtilities.CreateJsonSchema(new { id = "test" }.GetType()).ToString()) }, false);
// Add tools to the definition
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
@@ -902,12 +902,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
// Generate agent definition response with the tools
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -942,12 +942,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
definitionResponse.Tools.Add(functionTool);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -961,7 +961,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithDeclarativeFunctionFromDefinition_AcceptsDeclarativeFunctionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
@@ -974,7 +974,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -1001,12 +1001,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -1027,12 +1027,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync("test-agent", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
// Assert
Assert.NotNull(agent);
@@ -1083,7 +1083,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
new PromptAgentDefinition("test-model") { Instructions = "Test" },
tools);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
var options = new ChatClientAgentOptions
{
@@ -1092,7 +1092,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
var agent = await client.CreateAIAgentAsync("test-model", options);
var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -1278,14 +1278,14 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithClientFactory_ReceivesCorrectUnderlyingClientAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
IChatClient? receivedClient = null;
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync(
var agent = await testClient.Client.CreateAIAgentAsync(
"test-agent",
options,
clientFactory: (innerClient) =>
@@ -1340,10 +1340,10 @@ public sealed class AzureAIProjectChatClientExtensionsTests
const string AgentName = "test-agent";
const string Model = "test-model";
const string Instructions = "Test instructions";
AIProjectClient client = this.CreateTestAgentClient(AgentName, Instructions);
using var testClient = CreateTestAgentClientWithHandler(AgentName, Instructions);
// Act
var agent = await client.CreateAIAgentAsync(
var agent = await testClient.Client.CreateAIAgentAsync(
AgentName,
Model,
Instructions,
@@ -1367,12 +1367,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
var agentDefinitionResponse = GeneratePromptDefinitionResponse(definition, null);
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", agentDefinitionResponse: agentDefinitionResponse);
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: agentDefinitionResponse);
var options = new AgentVersionCreationOptions(definition);
// Act
var agent = await client.CreateAIAgentAsync(
var agent = await testClient.Client.CreateAIAgentAsync(
"test-agent",
options,
clientFactory: (innerClient) => new TestChatClient(innerClient));
@@ -1390,7 +1390,8 @@ public sealed class AzureAIProjectChatClientExtensionsTests
#region User-Agent Header Tests
/// <summary>
/// Verifies that the user-agent header is added to both synchronous and asynchronous requests made by agent creation methods.
/// Verifies that the MEAI user-agent header is added to CreateAIAgentAsync POST requests
/// via the protocol method's RequestOptions pipeline policy.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_UserAgentHeaderAddedToRequestsAsync()
@@ -1398,9 +1399,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
using var httpHandler = new HttpHandlerAssert(request =>
{
Assert.Equal("POST", request.Method.Method);
Assert.Contains("MEAI", request.Headers.UserAgent.ToString());
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
// Verify MEAI user-agent header is present on CreateAgentVersion POST request
Assert.True(request.Headers.TryGetValues("User-Agent", out var userAgentValues));
Assert.Contains(userAgentValues, v => v.Contains("MEAI"));
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
});
#pragma warning disable CA5399
@@ -1940,7 +1944,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithTextResponseFormat_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -1952,7 +1956,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -1966,7 +1970,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithoutSchema_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -1978,7 +1982,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -1992,7 +1996,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchema_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var options = new ChatClientAgentOptions
@@ -2006,7 +2010,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2020,7 +2024,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictMode_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var additionalProps = new AdditionalPropertiesDictionary
@@ -2039,7 +2043,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2053,7 +2057,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictModeFalse_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var additionalProps = new AdditionalPropertiesDictionary
@@ -2072,7 +2076,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2090,7 +2094,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithRawRepresentationFactory_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -2102,7 +2106,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2116,7 +2120,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNull_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -2128,7 +2132,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2142,7 +2146,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNonCreateResponseOptions_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -2154,7 +2158,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2172,7 +2176,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithDescription_SetsDescriptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient(description: "Test description");
using var testClient = CreateTestAgentClientWithHandler(description: "Test description");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -2181,7 +2185,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2195,7 +2199,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithoutDescription_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
@@ -2203,7 +2207,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2688,7 +2692,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
public async Task CreateAIAgentAsync_WithHostedToolTypes_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
using var testClient = CreateTestAgentClientWithHandler();
var webSearchTool = new HostedWebSearchTool();
var options = new ChatClientAgentOptions
@@ -2702,7 +2706,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
ChatClientAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
@@ -2855,6 +2859,54 @@ public sealed class AzureAIProjectChatClientExtensionsTests
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse);
}
/// <summary>
/// Creates a test AIProjectClient backed by an HTTP handler that returns canned responses.
/// Used for tests that exercise the protocol-method code path (CreateAgentVersion).
/// The returned client must be disposed to clean up the underlying HttpClient/handler.
/// </summary>
private static DisposableTestClient CreateTestAgentClientWithHandler(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
{
var responseJson = TestDataUtil.GetAgentVersionResponseJson(agentName, agentDefinitionResponse, instructions, description);
var httpHandler = new HttpHandlerAssert(_ =>
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(responseJson, Encoding.UTF8, "application/json") });
#pragma warning disable CA5399
var httpClient = new HttpClient(httpHandler);
#pragma warning restore CA5399
var client = new AIProjectClient(
new Uri("https://test.openai.azure.com/"),
new FakeAuthenticationTokenProvider(),
new() { Transport = new HttpClientPipelineTransport(httpClient) });
return new DisposableTestClient(client, httpClient, httpHandler);
}
/// <summary>
/// Wraps an AIProjectClient and its disposable dependencies for deterministic cleanup.
/// </summary>
private sealed class DisposableTestClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly HttpHandlerAssert _httpHandler;
public DisposableTestClient(AIProjectClient client, HttpClient httpClient, HttpHandlerAssert httpHandler)
{
this.Client = client;
this._httpClient = httpClient;
this._httpHandler = httpHandler;
}
public AIProjectClient Client { get; }
public void Dispose()
{
this._httpClient.Dispose();
this._httpHandler.Dispose();
}
}
/// <summary>
/// Creates a test AgentRecord for testing.
/// </summary>
@@ -3039,25 +3091,13 @@ public sealed class AzureAIProjectChatClientExtensionsTests
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
}
public override ClientResult CreateAgentVersion(string agentName, BinaryContent content, RequestOptions? options = null)
{
var responseJson = this.GetAgentVersionResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
}
public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
{
var responseJson = this.GetAgentVersionResponseJson();
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
}
public override Task<ClientResult> CreateAgentVersionAsync(string agentName, BinaryContent content, RequestOptions? options = null)
{
var responseJson = this.GetAgentVersionResponseJson();
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
}
public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, CancellationToken cancellationToken = default)
public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
{
var responseJson = this.GetAgentVersionResponseJson();
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
@@ -22,7 +22,7 @@ public class AzureAIProjectChatClientTests
var requestTriggered = false;
using var httpHandler = new HttpHandlerAssert(async (request) =>
{
if (request.RequestUri!.PathAndQuery.Contains("openai/responses"))
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
{
requestTriggered = true;
@@ -71,7 +71,7 @@ public class AzureAIProjectChatClientTests
var requestTriggered = false;
using var httpHandler = new HttpHandlerAssert(async (request) =>
{
if (request.RequestUri!.PathAndQuery.Contains("openai/responses"))
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
{
requestTriggered = true;
@@ -120,7 +120,7 @@ public class AzureAIProjectChatClientTests
var requestTriggered = false;
using var httpHandler = new HttpHandlerAssert(async (request) =>
{
if (request.RequestUri!.PathAndQuery.Contains("openai/responses"))
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
{
requestTriggered = true;
@@ -169,7 +169,7 @@ public class AzureAIProjectChatClientTests
var requestTriggered = false;
using var httpHandler = new HttpHandlerAssert(async (request) =>
{
if (request.RequestUri!.PathAndQuery.Contains("openai/responses"))
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
{
requestTriggered = true;
@@ -152,7 +152,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
[Fact]
[Trait("Category", "CosmosDB")]
public void StateKey_ReturnsDefaultKey_WhenNoStateKeyProvided()
public void StateKeys_ReturnsDefaultKey_WhenNoStateKeyProvided()
{
// Arrange & Act
this.SkipIfEmulatorNotAvailable();
@@ -161,12 +161,13 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
_ => new CosmosChatHistoryProvider.State("test-conversation"));
// Assert
Assert.Equal("CosmosChatHistoryProvider", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("CosmosChatHistoryProvider", provider.StateKeys);
}
[Fact]
[Trait("Category", "CosmosDB")]
public void StateKey_ReturnsCustomKey_WhenSetViaConstructor()
public void StateKeys_ReturnsCustomKey_WhenSetViaConstructor()
{
// Arrange & Act
this.SkipIfEmulatorNotAvailable();
@@ -176,7 +177,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
stateKey: "custom-key");
// Assert
Assert.Equal("custom-key", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("custom-key", provider.StateKeys);
}
[Fact]
@@ -109,7 +109,7 @@ public sealed class GitHubCopilotAgentTests
var hooks = new SessionHooks();
var infiniteSessions = new InfiniteSessionConfig();
var systemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = "Be helpful" };
PermissionHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
PermissionRequestHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
UserInputHandler userInputHandler = (_, _) => Task.FromResult(new UserInputResponse { Answer = "input" });
var mcpServers = new Dictionary<string, object> { ["server1"] = new McpLocalServerConfig() };
@@ -160,7 +160,7 @@ public sealed class GitHubCopilotAgentTests
var hooks = new SessionHooks();
var infiniteSessions = new InfiniteSessionConfig();
var systemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = "Be helpful" };
PermissionHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
PermissionRequestHandler permissionHandler = (_, _) => Task.FromResult(new PermissionRequestResult());
UserInputHandler userInputHandler = (_, _) => Task.FromResult(new UserInputResponse { Answer = "input" });
var mcpServers = new Dictionary<string, object> { ["server1"] = new McpLocalServerConfig() };
@@ -67,17 +67,18 @@ public sealed class Mem0ProviderTests : IDisposable
}
[Fact]
public void StateKey_ReturnsDefaultKey_WhenNoOptionsProvided()
public void StateKeys_ReturnsDefaultKey_WhenNoOptionsProvided()
{
// Arrange & Act
var provider = new Mem0Provider(this._httpClient, _ => new Mem0Provider.State(new Mem0ProviderScope { ThreadId = "tid" }));
// Assert
Assert.Equal("Mem0Provider", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("Mem0Provider", provider.StateKeys);
}
[Fact]
public void StateKey_ReturnsCustomKey_WhenSetViaOptions()
public void StateKeys_ReturnsCustomKey_WhenSetViaOptions()
{
// Arrange & Act
var provider = new Mem0Provider(
@@ -86,7 +87,8 @@ public sealed class Mem0ProviderTests : IDisposable
new Mem0ProviderOptions { StateKey = "custom-key" });
// Assert
Assert.Equal("custom-key", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("custom-key", provider.StateKeys);
}
[Fact]
@@ -419,7 +421,7 @@ public sealed class Mem0ProviderTests : IDisposable
}
[Fact]
public async Task StateKey_CanBeConfiguredViaOptionsAsync()
public async Task StateKeys_CanBeConfiguredViaOptionsAsync()
{
// Arrange
this._handler.EnqueueJsonResponse("[]");
@@ -380,7 +380,7 @@ public class AIContextProviderChatClientTests
/// </summary>
private sealed class TestAIContextProvider : AIContextProvider
{
private readonly string _stateKey;
private readonly IReadOnlyList<string> _stateKeys;
private readonly IEnumerable<ChatMessage> _provideMessages;
private readonly string? _provideInstructions;
private readonly IEnumerable<AITool>? _provideTools;
@@ -389,7 +389,7 @@ public class AIContextProviderChatClientTests
public InvokedContext? LastInvokedContext { get; private set; }
public override string StateKey => this._stateKey;
public override IReadOnlyList<string> StateKeys => this._stateKeys;
public TestAIContextProvider(
string stateKey,
@@ -397,7 +397,7 @@ public class AIContextProviderChatClientTests
string? provideInstructions = null,
IEnumerable<AITool>? provideTools = null)
{
this._stateKey = stateKey;
this._stateKeys = [stateKey];
this._provideMessages = provideMessages ?? [];
this._provideInstructions = provideInstructions;
this._provideTools = provideTools;
@@ -105,8 +105,8 @@ public partial class ChatClientAgentTests
ChatHistoryProvider = historyProvider
}));
Assert.Contains("SharedKey", ex.Message);
Assert.Contains(nameof(ChatHistoryProvider), ex.Message);
Assert.Contains("ChatHistoryProvider", ex.Message);
Assert.Contains("state key 'SharedKey'", ex.Message);
}
/// <summary>
@@ -159,11 +159,11 @@ public partial class ChatClientAgentTests
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
agent.RunAsync([new(ChatRole.User, "test")], session, options: new AgentRunOptions { AdditionalProperties = additionalProperties }));
Assert.Contains("SharedKey", ex.Message);
Assert.Contains("state key 'SharedKey'", ex.Message);
}
/// <summary>
/// Verify that RunAsync succeeds when an override ChatHistoryProvider uses the same StateKey as the default ChatHistoryProvider.
/// Verify that RunAsync succeeds when an override ChatHistoryProvider uses the same StateKeys as the default ChatHistoryProvider.
/// </summary>
[Fact]
public async Task RunAsync_SucceedsWhenOverrideChatHistoryProviderSharesKeyWithDefaultAsync()
@@ -192,6 +192,102 @@ public partial class ChatClientAgentTests
await agent.RunAsync([new(ChatRole.User, "test")], session, options: new AgentRunOptions { AdditionalProperties = additionalProperties });
}
/// <summary>
/// Verify that the constructor throws when two multi-key AIContextProviders have an overlapping key.
/// </summary>
[Fact]
public void Constructor_ThrowsWhenMultiKeyAIContextProvidersOverlap()
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
var provider1 = new MultiKeyTestAIContextProvider("Key1", "SharedKey");
var provider2 = new MultiKeyTestAIContextProvider("Key2", "SharedKey");
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
new ChatClientAgent(chatClient, options: new()
{
AIContextProviders = [provider1, provider2]
}));
Assert.Contains("state key 'SharedKey'", ex.Message);
}
/// <summary>
/// Verify that the constructor throws when a multi-key ChatHistoryProvider has an overlapping key with an AIContextProvider.
/// </summary>
[Fact]
public void Constructor_ThrowsWhenMultiKeyChatHistoryProviderOverlapsWithAIContextProvider()
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
var contextProvider = new MultiKeyTestAIContextProvider("Key1", "SharedKey");
var historyProvider = new MultiKeyTestChatHistoryProvider("Key2", "SharedKey");
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
new ChatClientAgent(chatClient, options: new()
{
AIContextProviders = [contextProvider],
ChatHistoryProvider = historyProvider
}));
Assert.Contains("state key 'SharedKey'", ex.Message);
}
/// <summary>
/// Verify that the constructor succeeds when multi-key providers have no overlapping keys.
/// </summary>
[Fact]
public void Constructor_SucceedsWithMultiKeyProvidersWithUniqueKeys()
{
// Arrange
var chatClient = new Mock<IChatClient>().Object;
var contextProvider1 = new MultiKeyTestAIContextProvider("Key1", "Key2");
var contextProvider2 = new MultiKeyTestAIContextProvider("Key3", "Key4");
var historyProvider = new MultiKeyTestChatHistoryProvider("Key5", "Key6");
// Act & Assert - should not throw
_ = new ChatClientAgent(chatClient, options: new()
{
AIContextProviders = [contextProvider1, contextProvider2],
ChatHistoryProvider = historyProvider
});
}
/// <summary>
/// Verify that RunAsync throws when a multi-key override ChatHistoryProvider has an overlapping key with an AIContextProvider.
/// </summary>
[Fact]
public async Task RunAsync_ThrowsWhenMultiKeyOverrideChatHistoryProviderClashesWithAIContextProviderAsync()
{
// Arrange
Mock<IChatClient> mockService = new();
mockService.Setup(
s => s.GetResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
var contextProvider = new MultiKeyTestAIContextProvider("Key1", "SharedKey");
var overrideHistoryProvider = new MultiKeyTestChatHistoryProvider("Key2", "SharedKey");
ChatClientAgent agent = new(mockService.Object, options: new()
{
AIContextProviders = [contextProvider]
});
// Act & Assert
ChatClientAgentSession? session = await agent.CreateSessionAsync() as ChatClientAgentSession;
AdditionalPropertiesDictionary additionalProperties = new();
additionalProperties.Add<ChatHistoryProvider>(overrideHistoryProvider);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
agent.RunAsync([new(ChatRole.User, "test")], session, options: new AgentRunOptions { AdditionalProperties = additionalProperties }));
Assert.Contains("state key 'SharedKey'", ex.Message);
}
#endregion
#region RunAsync Tests
@@ -489,6 +585,7 @@ public partial class ChatClientAgentTests
.ReturnsAsync(new ChatResponse(responseMessages));
var mockProvider = new Mock<AIContextProvider>(null, null, null);
mockProvider.SetupGet(p => p.StateKeys).Returns(["TestProvider"]);
mockProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -560,6 +657,7 @@ public partial class ChatClientAgentTests
.Throws(new InvalidOperationException("downstream failure"));
var mockProvider = new Mock<AIContextProvider>(null, null, null);
mockProvider.SetupGet(p => p.StateKeys).Returns(["TestProvider"]);
mockProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -618,6 +716,7 @@ public partial class ChatClientAgentTests
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
var mockProvider = new Mock<AIContextProvider>(null, null, null);
mockProvider.SetupGet(p => p.StateKeys).Returns(["TestProvider"]);
mockProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -678,7 +777,7 @@ public partial class ChatClientAgentTests
// Provider 1: adds a system message and a tool
var mockProvider1 = new Mock<AIContextProvider>(null, null, null);
mockProvider1.SetupGet(p => p.StateKey).Returns("Provider1");
mockProvider1.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockProvider1
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -697,7 +796,7 @@ public partial class ChatClientAgentTests
// Provider 2: adds another system message and verifies it receives accumulated context from provider 1
AIContext? provider2ReceivedContext = null;
var mockProvider2 = new Mock<AIContextProvider>(null, null, null);
mockProvider2.SetupGet(p => p.StateKey).Returns("Provider2");
mockProvider2.SetupGet(p => p.StateKeys).Returns(["Provider2"]);
mockProvider2
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -785,7 +884,7 @@ public partial class ChatClientAgentTests
.ThrowsAsync(new InvalidOperationException("downstream failure"));
var mockProvider1 = new Mock<AIContextProvider>(null, null, null);
mockProvider1.SetupGet(p => p.StateKey).Returns("Provider1");
mockProvider1.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockProvider1
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -802,7 +901,7 @@ public partial class ChatClientAgentTests
.Returns(new ValueTask());
var mockProvider2 = new Mock<AIContextProvider>(null, null, null);
mockProvider2.SetupGet(p => p.StateKey).Returns("Provider2");
mockProvider2.SetupGet(p => p.StateKeys).Returns(["Provider2"]);
mockProvider2
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -870,7 +969,7 @@ public partial class ChatClientAgentTests
.Returns(ToAsyncEnumerableAsync(responseUpdates));
var mockProvider1 = new Mock<AIContextProvider>(null, null, null);
mockProvider1.SetupGet(p => p.StateKey).Returns("Provider1");
mockProvider1.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockProvider1
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -887,7 +986,7 @@ public partial class ChatClientAgentTests
.Returns(new ValueTask());
var mockProvider2 = new Mock<AIContextProvider>(null, null, null);
mockProvider2.SetupGet(p => p.StateKey).Returns("Provider2");
mockProvider2.SetupGet(p => p.StateKeys).Returns(["Provider2"]);
mockProvider2
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -1829,6 +1928,7 @@ public partial class ChatClientAgentTests
.Returns(ToAsyncEnumerableAsync(responseUpdates));
var mockProvider = new Mock<AIContextProvider>(null, null, null);
mockProvider.SetupGet(p => p.StateKeys).Returns(["TestProvider"]);
mockProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -1908,6 +2008,7 @@ public partial class ChatClientAgentTests
.Throws(new InvalidOperationException("downstream failure"));
var mockProvider = new Mock<AIContextProvider>(null, null, null);
mockProvider.SetupGet(p => p.StateKeys).Returns(["TestProvider"]);
mockProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -1965,7 +2066,17 @@ public partial class ChatClientAgentTests
private sealed class TestAIContextProvider(string stateKey) : AIContextProvider
{
public override string StateKey => stateKey;
private readonly IReadOnlyList<string> _stateKeys = [stateKey];
public override IReadOnlyList<string> StateKeys => this._stateKeys;
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> new(context.AIContext);
}
private sealed class MultiKeyTestAIContextProvider(params string[] stateKeys) : AIContextProvider
{
public override IReadOnlyList<string> StateKeys => stateKeys;
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> new(context.AIContext);
@@ -1973,7 +2084,20 @@ public partial class ChatClientAgentTests
private sealed class TestChatHistoryProvider(string stateKey) : ChatHistoryProvider
{
public override string StateKey => stateKey;
private readonly IReadOnlyList<string> _stateKeys = [stateKey];
public override IReadOnlyList<string> StateKeys => this._stateKeys;
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> new(context.RequestMessages);
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
=> default;
}
private sealed class MultiKeyTestChatHistoryProvider(params string[] stateKeys) : ChatHistoryProvider
{
public override IReadOnlyList<string> StateKeys => stateKeys;
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
=> new(context.RequestMessages);
@@ -339,7 +339,7 @@ public class ChatClientAgent_BackgroundResponsesTests
// Create a mock chat history provider that would normally provide messages
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKey).Returns("ChatHistoryProvider");
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["ChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -347,7 +347,7 @@ public class ChatClientAgent_BackgroundResponsesTests
// Create a mock AI context provider that would normally provide context
var mockContextProvider = new Mock<AIContextProvider>(null, null, null);
mockContextProvider.SetupGet(p => p.StateKey).Returns("Provider1");
mockContextProvider.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockContextProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -408,7 +408,7 @@ public class ChatClientAgent_BackgroundResponsesTests
// Create a mock chat history provider that would normally provide messages
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKey).Returns("ChatHistoryProvider");
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["ChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -416,7 +416,7 @@ public class ChatClientAgent_BackgroundResponsesTests
// Create a mock AI context provider that would normally provide context
var mockContextProvider = new Mock<AIContextProvider>(null, null, null);
mockContextProvider.SetupGet(p => p.StateKey).Returns("Provider1");
mockContextProvider.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockContextProvider
.Protected()
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -639,7 +639,7 @@ public class ChatClientAgent_BackgroundResponsesTests
List<ChatMessage> capturedMessagesAddedToProvider = [];
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKey).Returns("ChatHistoryProvider");
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["ChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
@@ -648,7 +648,7 @@ public class ChatClientAgent_BackgroundResponsesTests
AIContextProvider.InvokedContext? capturedInvokedContext = null;
var mockContextProvider = new Mock<AIContextProvider>(null, null, null);
mockContextProvider.SetupGet(p => p.StateKey).Returns("Provider1");
mockContextProvider.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockContextProvider
.Protected()
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
@@ -703,7 +703,7 @@ public class ChatClientAgent_BackgroundResponsesTests
List<ChatMessage> capturedMessagesAddedToProvider = [];
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKey).Returns("ChatHistoryProvider");
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["ChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
@@ -712,7 +712,7 @@ public class ChatClientAgent_BackgroundResponsesTests
AIContextProvider.InvokedContext? capturedInvokedContext = null;
var mockContextProvider = new Mock<AIContextProvider>(null, null, null);
mockContextProvider.SetupGet(p => p.StateKey).Returns("Provider1");
mockContextProvider.SetupGet(p => p.StateKeys).Returns(["Provider1"]);
mockContextProvider
.Protected()
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
@@ -186,6 +186,7 @@ public class ChatClientAgent_ChatHistoryManagementTests
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
Mock<ChatHistoryProvider> mockChatHistoryProvider = new(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -241,6 +242,7 @@ public class ChatClientAgent_ChatHistoryManagementTests
It.IsAny<CancellationToken>())).Throws(new InvalidOperationException("Test Error"));
Mock<ChatHistoryProvider> mockChatHistoryProvider = new(null, null, null);
mockChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]);
mockChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -430,6 +432,7 @@ public class ChatClientAgent_ChatHistoryManagementTests
// Arrange a chat history provider to override the factory provided one.
Mock<ChatHistoryProvider> mockOverrideChatHistoryProvider = new(null, null, null);
mockOverrideChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]);
mockOverrideChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -443,6 +446,7 @@ public class ChatClientAgent_ChatHistoryManagementTests
// Arrange a chat history provider to provide to the agent at construction time.
// This one shouldn't be used since it is being overridden.
Mock<ChatHistoryProvider> mockAgentOptionsChatHistoryProvider = new(null, null, null);
mockAgentOptionsChatHistoryProvider.SetupGet(p => p.StateKeys).Returns(["TestChatHistoryProvider"]);
mockAgentOptionsChatHistoryProvider
.Protected()
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
@@ -39,17 +39,18 @@ public sealed class TextSearchProviderTests
}
[Fact]
public void StateKey_ReturnsDefaultKey_WhenNoOptionsProvided()
public void StateKeys_ReturnsDefaultKey_WhenNoOptionsProvided()
{
// Arrange & Act
var provider = new TextSearchProvider((_, _) => Task.FromResult<IEnumerable<TextSearchProvider.TextSearchResult>>([]));
// Assert
Assert.Equal("TextSearchProvider", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("TextSearchProvider", provider.StateKeys);
}
[Fact]
public void StateKey_ReturnsCustomKey_WhenSetViaOptions()
public void StateKeys_ReturnsCustomKey_WhenSetViaOptions()
{
// Arrange & Act
var provider = new TextSearchProvider(
@@ -57,7 +58,8 @@ public sealed class TextSearchProviderTests
new TextSearchProviderOptions { StateKey = "custom-key" });
// Assert
Assert.Equal("custom-key", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("custom-key", provider.StateKeys);
}
[Theory]
@@ -56,7 +56,7 @@ public class ChatHistoryMemoryProviderTests
}
[Fact]
public void StateKey_ReturnsDefaultKey_WhenNoOptionsProvided()
public void StateKeys_ReturnsDefaultKey_WhenNoOptionsProvided()
{
// Arrange & Act
var provider = new ChatHistoryMemoryProvider(
@@ -66,11 +66,12 @@ public class ChatHistoryMemoryProviderTests
_ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }));
// Assert
Assert.Equal("ChatHistoryMemoryProvider", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("ChatHistoryMemoryProvider", provider.StateKeys);
}
[Fact]
public void StateKey_ReturnsCustomKey_WhenSetViaOptions()
public void StateKeys_ReturnsCustomKey_WhenSetViaOptions()
{
// Arrange & Act
var provider = new ChatHistoryMemoryProvider(
@@ -81,7 +82,8 @@ public class ChatHistoryMemoryProviderTests
new ChatHistoryMemoryProviderOptions { StateKey = "custom-key" });
// Assert
Assert.Equal("custom-key", provider.StateKey);
Assert.Single(provider.StateKeys);
Assert.Contains("custom-key", provider.StateKeys);
}
[Fact]
@@ -14,7 +14,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
public sealed class DeclarativeCodeGenTest(ITestOutputHelper output) : WorkflowTest(output)
{
[Theory]
[InlineData("CheckSystem.yaml", "CheckSystem.json")]
[InlineData("CheckSystem.yaml", "CheckSystem.json", Skip = "Temporarily skipped")]
[InlineData("SendActivity.yaml", "SendActivity.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json", true)]
@@ -15,7 +15,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : WorkflowTest(output)
{
[Theory]
[InlineData("CheckSystem.yaml", "CheckSystem.json")]
[InlineData("CheckSystem.yaml", "CheckSystem.json", Skip = "Temporarily skipped")]
[InlineData("ConversationMessages.yaml", "ConversationMessages.json")]
[InlineData("ConversationMessages.yaml", "ConversationMessages.json", true)]
[InlineData("InputArguments.yaml", "InputArguments.json")]
@@ -33,7 +33,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
public Task ValidateScenarioAsync(string workflowFileName, string testcaseFileName, bool externalConveration = false) =>
this.RunWorkflowAsync(GetWorkflowPath(workflowFileName, isSample: true), testcaseFileName, externalConveration);
[Theory]
[Theory(Skip = "Multi-turn tests hang in CI - needs investigation")]
[InlineData("ConfirmInput.yaml", "ConfirmInput.json", false)]
[InlineData("RequestExternalInput.yaml", "RequestExternalInput.json", false)]
public Task ValidateMultiTurnAsync(string workflowFileName, string testcaseFileName, bool isSample) =>
@@ -1,9 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
namespace OpenAIAssistant.IntegrationTests;
public class OpenAIAssistantStructuredOutputRunTests() : StructuredOutputRunTests<OpenAIAssistantFixture>(() => new())
{
private const string SkipReason = "Fails intermittently on the build agent/CI";
[Fact(Skip = SkipReason)]
public override Task RunWithResponseFormatReturnsExpectedResultAsync() =>
base.RunWithResponseFormatReturnsExpectedResultAsync();
[Fact(Skip = SkipReason)]
public override Task RunWithGenericTypeReturnsExpectedResultAsync() =>
base.RunWithGenericTypeReturnsExpectedResultAsync();
[Fact(Skip = SkipReason)]
public override Task RunWithPrimitiveTypeReturnsExpectedResultAsync() =>
base.RunWithPrimitiveTypeReturnsExpectedResultAsync();
}