mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
e7961571a8
* Update Microsoft.Agents.AI.AzureAI for Azure.AI.Projects SDK 2.0.0 - Bump Azure.AI.Projects to 2.0.0-alpha.20260213.1 - Bump Azure.AI.Projects.OpenAI to 2.0.0-alpha.20260213.1 - Bump System.ClientModel to 1.9.0 (transitive dependency) - Switch both GetAgent and CreateAgentVersion to protocol methods with MEAI user-agent policy injection via RequestOptions - Migrate 29 CREATE-path tests from FakeAgentClient to HttpHandlerAssert pattern for real HTTP pipeline testing - Fix StructuredOutputDefinition constructor (BinaryData -> IDictionary) - Fix responses endpoint path (openai/responses -> /responses) - Add local-packages NuGet source for pre-release nupkgs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update Azure.AI.Projects to 2.0.0-beta.1 from NuGet.org - Update Azure.AI.Projects and Azure.AI.Projects.OpenAI to 2.0.0-beta.1 - Remove local-packages NuGet source (packages now on nuget.org) - Fix MemorySearchTool -> MemorySearchPreviewTool rename - Fix RedTeams.CreateAsync ambiguous call - Fix CreateAgentVersion/Async signature change (BinaryData -> string) - Suppress AAIP001 experimental warning for WorkflowAgentDefinition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move s_modelWriterOptionsWire field before methods that use it Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix flaky test: prevent spurious workflow_invoke Activity on timeout wake-up The StreamingRunEventStream run loop uses a 1-second timeout on WaitForInputAsync. When the timeout fires before the consumer calls StopAsync, the loop would create a spurious workflow_invoke Activity even though no actual input was provided. This caused the WorkflowRunActivity_IsStopped_Streaming_OffThread_MultiTurnAsync test to intermittently fail (expecting 2 activities but finding 3). Fix: guard the loop body with a HasUnprocessedMessages check. On timeout wake-ups with no work, the loop waits again without creating an activity or changing the run status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix epoch race condition causing unit tests to hang on net10.0 and net472 The HasUnprocessedMessages guard (previous commit) correctly prevents spurious workflow_invoke Activity creation on timeout wake-ups, but exposed a latent race in the epoch-based signal filtering. The race: when the run loop processes messages quickly and calls Interlocked.Increment(ref _completionEpoch) before the consumer calls TakeEventStreamAsync, the consumer reads the already-incremented epoch and sets myEpoch = epoch + 1. This causes the consumer to skip the valid InternalHaltSignal (its epoch < myEpoch) and block forever waiting for a signal that will never arrive (since the guard prevents spurious signal generation). Fix: read _completionEpoch without +1. The +1 was originally needed to filter stale signals from timeout-driven spurious loop iterations, but those no longer exist thanks to the HasUnprocessedMessages guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert "Fix epoch race condition causing unit tests to hang on net10.0 and net472" This reverts commit6ce7f01be8. * Revert "Fix flaky test: prevent spurious workflow_invoke Activity on timeout wake-up" This reverts commit98963e17f2. * Skip hanging multi-turn declarative integration tests The ValidateMultiTurnAsync tests (ConfirmInput.yaml, RequestExternalInput.yaml) hang indefinitely in CI, blocking the merge queue. The hang is SDK-independent (reproduces with both Azure.AI.Projects 1.2.0-beta.5 and 2.0.0-beta.1) and is a pre-existing issue in the declarative workflow multi-turn test logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused using directive in IntegrationTest.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore Azure.AI.Projects 2.0.0-beta.1 version bump The merge from main accidentally reverted the package versions back to 1.2.0-beta.5. This is the primary change of this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address merge conflict * Skip flaky WorkflowRunActivity_IsStopped_Streaming_OffThread_MultiTurnAsync test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip CheckSystem test cases temporarily Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
211 lines
9.1 KiB
C#
211 lines
9.1 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.ClientModel.Primitives;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Azure.AI.Projects;
|
|
|
|
namespace Microsoft.Agents.AI.AzureAI.UnitTests;
|
|
|
|
public class AzureAIProjectChatClientTests
|
|
{
|
|
/// <summary>
|
|
/// Verify that when the ChatOptions has a "conv_" prefixed conversation ID, the chat client uses conversation in the http requests via the chat client
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task ChatClient_UsesDefaultConversationIdAsync()
|
|
{
|
|
// Arrange
|
|
var requestTriggered = false;
|
|
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
|
{
|
|
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
|
|
{
|
|
requestTriggered = true;
|
|
|
|
// Assert
|
|
if (request.Content is not null)
|
|
{
|
|
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
Assert.Contains("conv_12345", requestBody);
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetOpenAIDefaultResponseJson(), Encoding.UTF8, "application/json") };
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using 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) });
|
|
|
|
var agent = await client.GetAIAgentAsync(
|
|
new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_12345" }
|
|
});
|
|
|
|
// Act
|
|
var session = await agent.CreateSessionAsync();
|
|
await agent.RunAsync("Hello", session);
|
|
|
|
Assert.True(requestTriggered);
|
|
var chatClientSession = Assert.IsType<ChatClientAgentSession>(session);
|
|
Assert.Equal("conv_12345", chatClientSession.ConversationId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the chat client doesn't have a default "conv_" conversation id, the chat client still uses the conversation ID in HTTP requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task ChatClient_UsesPerRequestConversationId_WhenNoDefaultConversationIdIsProvidedAsync()
|
|
{
|
|
// Arrange
|
|
var requestTriggered = false;
|
|
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
|
{
|
|
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
|
|
{
|
|
requestTriggered = true;
|
|
|
|
// Assert
|
|
if (request.Content is not null)
|
|
{
|
|
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
Assert.Contains("conv_12345", requestBody);
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetOpenAIDefaultResponseJson(), Encoding.UTF8, "application/json") };
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using 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) });
|
|
|
|
var agent = await client.GetAIAgentAsync(
|
|
new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" },
|
|
});
|
|
|
|
// Act
|
|
var session = await agent.CreateSessionAsync();
|
|
await agent.RunAsync("Hello", session, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "conv_12345" } });
|
|
|
|
Assert.True(requestTriggered);
|
|
var chatClientSession = Assert.IsType<ChatClientAgentSession>(session);
|
|
Assert.Equal("conv_12345", chatClientSession.ConversationId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that even when the chat client has a default conversation id, the chat client will prioritize the per-request conversation id provided in HTTP requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task ChatClient_UsesPerRequestConversationId_EvenWhenDefaultConversationIdIsProvidedAsync()
|
|
{
|
|
// Arrange
|
|
var requestTriggered = false;
|
|
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
|
{
|
|
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
|
|
{
|
|
requestTriggered = true;
|
|
|
|
// Assert
|
|
if (request.Content is not null)
|
|
{
|
|
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
Assert.Contains("conv_12345", requestBody);
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetOpenAIDefaultResponseJson(), Encoding.UTF8, "application/json") };
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using 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) });
|
|
|
|
var agent = await client.GetAIAgentAsync(
|
|
new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_should_not_use_default" }
|
|
});
|
|
|
|
// Act
|
|
var session = await agent.CreateSessionAsync();
|
|
await agent.RunAsync("Hello", session, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "conv_12345" } });
|
|
|
|
Assert.True(requestTriggered);
|
|
var chatClientSession = Assert.IsType<ChatClientAgentSession>(session);
|
|
Assert.Equal("conv_12345", chatClientSession.ConversationId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the chat client is provided without a "conv_" prefixed conversation ID, the chat client uses the previous conversation ID in HTTP requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task ChatClient_UsesPreviousResponseId_WhenConversationIsNotPrefixedAsConvAsync()
|
|
{
|
|
// Arrange
|
|
var requestTriggered = false;
|
|
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
|
{
|
|
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
|
|
{
|
|
requestTriggered = true;
|
|
|
|
// Assert
|
|
if (request.Content is not null)
|
|
{
|
|
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
Assert.Contains("resp_0888a", requestBody);
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetOpenAIDefaultResponseJson(), Encoding.UTF8, "application/json") };
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using 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) });
|
|
|
|
var agent = await client.GetAIAgentAsync(
|
|
new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" },
|
|
});
|
|
|
|
// Act
|
|
var session = await agent.CreateSessionAsync();
|
|
await agent.RunAsync("Hello", session, options: new ChatClientAgentRunOptions() { ChatOptions = new() { ConversationId = "resp_0888a" } });
|
|
|
|
Assert.True(requestTriggered);
|
|
var chatClientSession = Assert.IsType<ChatClientAgentSession>(session);
|
|
Assert.Equal("resp_0888a46cbf2b1ff3006914596e05d08195a77c3f5187b769a7", chatClientSession.ConversationId);
|
|
}
|
|
}
|