// 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.Extensions.OpenAI; using Azure.AI.Projects; namespace Microsoft.Agents.AI.Foundry.UnitTests; #pragma warning disable CS0618 public class AzureAIProjectChatClientTests { /// /// Verify that after the first RunAsync, the session's ConversationId is set from the /// response, and subsequent requests include that conversation ID automatically. /// [Fact] public async Task ChatClient_UsesDefaultConversationIdAsync() { // Arrange var responsesRequestCount = 0; using var httpHandler = new HttpHandlerAssert(async (request) => { if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses")) { responsesRequestCount++; // Assert: On the second Responses API call, verify the conversation ID // from the first response is automatically included in the request body. if (responsesRequestCount == 2 && 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 AIProjectClient projectClient = new( new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new AIProjectClientOptions() { Transport = new HttpClientPipelineTransport(httpClient) }); var agent = projectClient.AsAIAgent(new AgentReference("agent-name")); // Act var session = await agent.CreateSessionAsync(); await agent.RunAsync("Hello", session); await agent.RunAsync("Follow up", session); // Assert Assert.Equal(2, responsesRequestCount); var chatClientSession = Assert.IsType(session); Assert.Equal("resp_0888a46cbf2b1ff3006914596e05d08195a77c3f5187b769a7", chatClientSession.ConversationId); } /// /// 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. /// [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 AIProjectClient projectClient = new( new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new AIProjectClientOptions() { Transport = new HttpClientPipelineTransport(httpClient) }); var agent = projectClient.AsAIAgent(new AgentReference("agent-name")); // 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(session); Assert.Equal("conv_12345", chatClientSession.ConversationId); } /// /// 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. /// [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 AIProjectClient projectClient = new( new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new AIProjectClientOptions() { Transport = new HttpClientPipelineTransport(httpClient) }); var agent = projectClient.AsAIAgent(new AgentReference("agent-name")); // 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(session); Assert.Equal("conv_12345", chatClientSession.ConversationId); } /// /// 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. /// [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 AIProjectClient projectClient = new( new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new AIProjectClientOptions() { Transport = new HttpClientPipelineTransport(httpClient) }); var agent = projectClient.AsAIAgent(new AgentReference("agent-name")); // 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(session); Assert.Equal("resp_0888a46cbf2b1ff3006914596e05d08195a77c3f5187b769a7", chatClientSession.ConversationId); } } #pragma warning restore CS0618