Files
agent-framework/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
T
SergeyMenshykh 9506fb28f6 .NET: [Breaking] Structured Output improvements (#3761)
* .NET: Delete AgentResponse.{Try}Deserialize<T> methods (#3518)

* delete deserialize method of agent response

* order usings

* Update dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/SharedStateAgent.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/AGUIClientServer/AGUIDojoServer/SharedState/SharedStateAgent.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .NET:[Breaking] Add support for structured output (#3658)

* add support for so

* restore lost xml comment part

* fix using ordering

* Update dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgentStructuredOutput.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgentStructuredOutput.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_SO_WithFormatResponseTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* addressw pr review comments

* address pr review feedback

* address pr review comments

* fix compilation issues after the latest merge with main

* remove unnecessry options

* remove RunAsync<object> methods

* address code review feedback

* address pr review feedback

* make copy constructor protected

* address pr review feedback

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* .NET: Add decorator for structured output support (#3694)

* add decorator that adds structured output support to agents that don't natively support it.

* Update dotnet/src/Microsoft.Agents.AI/StructuredOutput/StructuredOutputAgentResponse.cs

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* Update dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* address pr review feedback

---------

Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>

* .NET: Support primitives and arrays for SO (#3696)

* wrap primitives and arrays

* fix file encoding

* address review comments

* add adr

* add missed change

* fix compilation issue

* address review comments

* rename adr file name

* reflect decision to have SO decorator as a reference implementation in samples

* .NET: Move SO agent to samples (#3820)

* move SO agent to samples

* change file encoding

* fix files encoding

* .NET: Preserve caller context (#3803)

* fix stuck orchestration

* add previously removed RunAsync<T> method to DurableAIAgent

* suppress IDE0005 warning

* update changelog and remove unused constructor of AgentResponse<T>

* updatge the changelog

* address PR review feedback

* .NET: Disable irrelevant integration test (#3913)

* disable irrelevant integration test

* Update dotnet/tests/AzureAI.IntegrationTests/AIProjectClientAgentStructuredOutputRunTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* forgotten change

* address pr review feedback

* disable intermittently failing integration test.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
2026-02-13 17:03:51 +00:00

183 lines
7.2 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
using Shared.IntegrationTests;
namespace AzureAI.IntegrationTests;
public class AIProjectClientFixture : IChatClientAgentFixture
{
private static readonly AzureAIConfiguration s_config = TestConfiguration.LoadSection<AzureAIConfiguration>();
private ChatClientAgent _agent = null!;
private AIProjectClient _client = null!;
public IChatClient ChatClient => this._agent.ChatClient;
public AIAgent Agent => this._agent;
public async Task<string> CreateConversationAsync()
{
var response = await this._client.GetProjectOpenAIClient().GetProjectConversationsClient().CreateProjectConversationAsync();
return response.Value.Id;
}
public async Task<List<ChatMessage>> GetChatHistoryAsync(AIAgent agent, AgentSession session)
{
var chatClientSession = (ChatClientAgentSession)session;
if (chatClientSession.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
{
// Conversation sessions do not persist message history.
return await this.GetChatHistoryFromConversationAsync(chatClientSession.ConversationId);
}
if (chatClientSession.ConversationId?.StartsWith("resp_", StringComparison.OrdinalIgnoreCase) == true)
{
return await this.GetChatHistoryFromResponsesChainAsync(chatClientSession.ConversationId);
}
var chatHistoryProvider = agent.GetService<ChatHistoryProvider>();
if (chatHistoryProvider is null)
{
return [];
}
return (await chatHistoryProvider.InvokingAsync(new(agent, session, []))).ToList();
}
private async Task<List<ChatMessage>> GetChatHistoryFromResponsesChainAsync(string conversationId)
{
var openAIResponseClient = this._client.GetProjectOpenAIClient().GetProjectResponsesClient();
var inputItems = await openAIResponseClient.GetResponseInputItemsAsync(conversationId).ToListAsync();
var response = await openAIResponseClient.GetResponseAsync(conversationId);
var responseItem = response.Value.OutputItems.FirstOrDefault()!;
// Take the messages that were the chat history leading up to the current response
// remove the instruction messages, and reverse the order so that the most recent message is last.
var previousMessages = inputItems
.Select(ConvertToChatMessage)
.Where(x => x.Text != "You are a helpful assistant.")
.Reverse();
// Convert the response item to a chat message.
var responseMessage = ConvertToChatMessage(responseItem);
// Concatenate the previous messages with the response message to get a full chat history
// that includes the current response.
return [.. previousMessages, responseMessage];
}
private static ChatMessage ConvertToChatMessage(ResponseItem item)
{
if (item is MessageResponseItem messageResponseItem)
{
var role = messageResponseItem.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant;
return new ChatMessage(role, messageResponseItem.Content.FirstOrDefault()?.Text);
}
throw new NotSupportedException("This test currently only supports text messages");
}
private async Task<List<ChatMessage>> GetChatHistoryFromConversationAsync(string conversationId)
{
List<ChatMessage> messages = [];
await foreach (AgentResponseItem item in this._client.GetProjectOpenAIClient().GetProjectConversationsClient().GetProjectConversationItemsAsync(conversationId, order: "asc"))
{
var openAIItem = item.AsResponseResultItem();
if (openAIItem is MessageResponseItem messageItem)
{
messages.Add(new ChatMessage
{
Role = new ChatRole(messageItem.Role.ToString()),
Contents = messageItem.Content
.Where(c => c.Kind is ResponseContentPartKind.OutputText or ResponseContentPartKind.InputText)
.Select(c => new TextContent(c.Text))
.ToList<AIContent>()
});
}
}
return messages;
}
public async Task<ChatClientAgent> CreateChatClientAgentAsync(
string name = "HelpfulAssistant",
string instructions = "You are a helpful assistant.",
IList<AITool>? aiTools = null)
{
return await this._client.CreateAIAgentAsync(GenerateUniqueAgentName(name), model: s_config.DeploymentName, instructions: instructions, tools: aiTools);
}
public async Task<ChatClientAgent> CreateChatClientAgentAsync(ChatClientAgentOptions options)
{
options.Name ??= GenerateUniqueAgentName("HelpfulAssistant");
return await this._client.CreateAIAgentAsync(model: s_config.DeploymentName, options);
}
public static string GenerateUniqueAgentName(string baseName) =>
$"{baseName}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
public Task DeleteAgentAsync(ChatClientAgent agent) =>
this._client.Agents.DeleteAgentAsync(agent.Name);
public async Task DeleteSessionAsync(AgentSession session)
{
var typedSession = (ChatClientAgentSession)session;
if (typedSession.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
{
await this._client.GetProjectOpenAIClient().GetProjectConversationsClient().DeleteConversationAsync(typedSession.ConversationId);
}
else if (typedSession.ConversationId?.StartsWith("resp_", StringComparison.OrdinalIgnoreCase) == true)
{
await this.DeleteResponseChainAsync(typedSession.ConversationId!);
}
}
private async Task DeleteResponseChainAsync(string lastResponseId)
{
var response = await this._client.GetProjectOpenAIClient().GetProjectResponsesClient().GetResponseAsync(lastResponseId);
await this._client.GetProjectOpenAIClient().GetProjectResponsesClient().DeleteResponseAsync(lastResponseId);
if (response.Value.PreviousResponseId is not null)
{
await this.DeleteResponseChainAsync(response.Value.PreviousResponseId);
}
}
public Task DisposeAsync()
{
if (this._client is not null && this._agent is not null)
{
return this._client.Agents.DeleteAgentAsync(this._agent.Name);
}
return Task.CompletedTask;
}
public virtual async Task InitializeAsync()
{
this._client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
this._agent = await this.CreateChatClientAgentAsync();
}
public async Task InitializeAsync(ChatClientAgentOptions options)
{
this._client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
this._agent = await this.CreateChatClientAgentAsync(options);
}
}