.NET: Clean / address some message warnings (#291)

* WIP

* Structured Output sample

* Update dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs

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

* Address xml and comment targeting the Structured Output context

* Update with proposed fix for Persistent ChatClient

* Address PR feedback

* Address minor warnings

* Address initialization

* Address initialization

* Address PR comments, update suggestions

* Revert changes to NullableAttributese.cs

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
Roger Barreto
2025-08-05 13:55:44 +01:00
committed by GitHub
Unverified
parent cbb05e210f
commit 9faa27b8eb
19 changed files with 63 additions and 73 deletions
+6
View File
@@ -246,6 +246,12 @@ dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
dotnet_diagnostic.IDE0046.severity = suggestion # If statement can be simplified
dotnet_diagnostic.IDE0056.severity = suggestion # Indexing can be simplified
dotnet_diagnostic.IDE0057.severity = suggestion # Substring can be simplified
dotnet_diagnostic.IDE0079.severity = none # Remove unnecessary suppression
dotnet_diagnostic.IDE0290.severity = none # Use primary constructor
dotnet_diagnostic.IDE0046.severity = none # if statement can be simplified
dotnet_diagnostic.IDE0057.severity = none # Substring can be simplified
dotnet_diagnostic.IDE0042.severity = none # Variable declaration can be deconstructed
dotnet_diagnostic.IDE0047.severity = none # Parentheses can be removed
dotnet_diagnostic.CA2007.severity = error # Do not directly await a Task
dotnet_diagnostic.VSTHRD111.severity = error # Use .ConfigureAwait(bool)
@@ -105,26 +105,21 @@ public sealed class Step03_ChatClientAgent_UsingCodeInterpreterTools(ITestOutput
/// <param name="provider">Provider of the chat client that is used to determine how to extract the output.</param>
/// <returns>The code interpreter output as a string.</returns>
private static string? GetCodeInterpreterOutput(object rawRepresentation, ChatClientProviders provider)
{
switch (provider)
=> provider switch
{
case ChatClientProviders.OpenAIAssistant
when rawRepresentation is OpenAI.Assistants.RunStepDetailsUpdate stepDetails:
return $"{stepDetails.CodeInterpreterInput}{string.Join(
string.Empty,
stepDetails.CodeInterpreterOutputs.SelectMany(l => l.Logs)
)}";
ChatClientProviders.OpenAIAssistant
when rawRepresentation is OpenAI.Assistants.RunStepDetailsUpdate stepDetails => $"{stepDetails.CodeInterpreterInput}{string.Join(
string.Empty,
stepDetails.CodeInterpreterOutputs.SelectMany(l => l.Logs)
)}",
case ChatClientProviders.AzureAIAgentsPersistent
when rawRepresentation is Azure.AI.Agents.Persistent.RunStepDetailsUpdate stepDetails:
return $"{stepDetails.CodeInterpreterInput}{string.Join(
string.Empty,
stepDetails.CodeInterpreterOutputs.OfType<RunStepDeltaCodeInterpreterLogOutput>().SelectMany(l => l.Logs)
)}";
}
return null;
}
ChatClientProviders.AzureAIAgentsPersistent
when rawRepresentation is Azure.AI.Agents.Persistent.RunStepDetailsUpdate stepDetails => $"{stepDetails.CodeInterpreterInput}{string.Join(
string.Empty,
stepDetails.CodeInterpreterOutputs.OfType<RunStepDeltaCodeInterpreterLogOutput>().SelectMany(l => l.Logs)
)}",
_ => null,
};
#endregion
}
@@ -40,7 +40,7 @@ public sealed class Step04_ChatClientAgent_DependencyInjection(ITestOutputHelper
loggerFactory: sp.GetRequiredService<ILoggerFactory>()));
// Build the service provider.
using var serviceProvider = services.BuildServiceProvider();
await using var serviceProvider = services.BuildServiceProvider();
// Get the agent from the service provider.
var agent = serviceProvider.GetRequiredService<AIAgent>();
@@ -23,14 +23,16 @@ public sealed class Step06_ChatClientAgent_StructuredOutputs(ITestOutputHelper o
[InlineData(ChatClientProviders.OpenAIResponses)]
public async Task RunWithCustomSchema(ChatClientProviders provider)
{
var agentOptions = new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
agentOptions.ChatOptions = new()
var agentOptions = new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")
{
ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
schemaName: "PersonInfo",
schemaDescription: "Information about a person including their name, age, and occupation"
)
ChatOptions = new()
{
ResponseFormat = ChatResponseFormatJson.ForJsonSchema(
schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
schemaName: "PersonInfo",
schemaDescription: "Information about a person including their name, age, and occupation"
)
}
};
// Create the server-side agent Id when applicable (depending on the provider).
@@ -100,17 +100,12 @@ public sealed class Step07_ChatClientAgent_UsingFileSearchTools(ITestOutputHelpe
}
private Task<string> CreateVectorStoreAsync(IEnumerable<string> fileIds, ChatClientProviders provider)
{
switch (provider)
=> provider switch
{
case ChatClientProviders.OpenAIAssistant:
return CreateVectorStoreOpenAIAssistantAsync(fileIds);
case ChatClientProviders.AzureAIAgentsPersistent:
return CreateVectorStoreAzureAIAgentsPersistentAsync(fileIds);
default:
throw new NotSupportedException($"Client provider {provider} is not supported.");
}
}
ChatClientProviders.OpenAIAssistant => CreateVectorStoreOpenAIAssistantAsync(fileIds),
ChatClientProviders.AzureAIAgentsPersistent => CreateVectorStoreAzureAIAgentsPersistentAsync(fileIds),
_ => throw new NotSupportedException($"Client provider {provider} is not supported."),
};
private async Task<string> CreateVectorStoreOpenAIAssistantAsync(IEnumerable<string> fileIds)
{
@@ -33,7 +33,7 @@ public class AgentClient(HttpClient httpClient, ILogger<AgentClient> logger)
using var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var reader = new StreamReader(stream);
string? line;
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable CA1019
#pragma warning disable CA1019, RCS1251, IDE0300
namespace System.Diagnostics.CodeAnalysis;
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
#pragma warning disable RCS1157 // Composite enum value contains undefined flag
namespace System.Diagnostics.CodeAnalysis;
/// <summary>
@@ -41,12 +41,17 @@ public partial class ConcurrentOrchestration : OrchestratingAgent
return f;
}
return static async (responses, cancellationToken) =>
new AgentRunResponse([.. responses.Where(r => r.Messages.Count > 0).Select(r =>
{
var messages = r.Messages;
return messages.Count > 0 ? messages[messages.Count - 1] : new();
})]);
return (responses, cancellationToken)
=> Task.FromResult(
new AgentRunResponse([.. responses
.Where(r => r.Messages.Count > 0)
.Select(r =>
{
var messages = r.Messages;
return messages.Count > 0 ? messages[messages.Count - 1] : new();
})
])
);
}
set => this._aggregationFunc = value;
}
@@ -509,6 +509,7 @@ namespace Azure.AI.Agents.Persistent
// We need to extract the run ID and ensure that the ToolOutput we send back to Azure
// is only the call ID.
string[]? runAndCallIDs;
#pragma warning disable CA1031 // Do not catch general exception types
try
{
runAndCallIDs = JsonSerializer.Deserialize(frc.CallId, AgentsChatClientJsonContext.Default.StringArray);
@@ -517,6 +518,7 @@ namespace Azure.AI.Agents.Persistent
{
continue;
}
#pragma warning restore CA1031 // Do not catch general exception types
if (runAndCallIDs is null ||
runAndCallIDs.Length != 2 ||
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -126,7 +125,7 @@ public sealed class ChatClientAgent : AIAgent
}
// Convert the chat response messages to a valid IReadOnlyCollection for notification signatures below.
var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection<ChatMessage> ?? chatResponse.Messages.ToArray();
var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection<ChatMessage> ?? [.. chatResponse.Messages];
await this.NotifyThreadOfNewMessagesAsync(chatClientThread, chatResponseMessages, cancellationToken).ConfigureAwait(false);
@@ -174,7 +173,7 @@ public sealed class ChatClientAgent : AIAgent
}
var chatResponse = responseUpdates.ToChatResponse();
var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection<ChatMessage> ?? chatResponse.Messages.ToArray();
var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection<ChatMessage> ?? [.. chatResponse.Messages];
// We can derive the type of supported thread from whether we have a conversation id,
// so let's update it and set the conversation id for the service thread case.
@@ -72,28 +72,11 @@ public sealed class TestConfiguration
this._configRoot = configRoot;
}
/// <summary>
/// Provides access to the configuration root for the application.
/// </summary>
private static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot;
/// <summary>
/// Gets the configuration settings for the AzureAI integration.
/// </summary>
public static AzureAIConfig AzureAI => LoadSection<AzureAIConfig>();
/// <summary>
/// Retrieves a configuration section based on the specified key.
/// </summary>
/// <param name="caller">The key identifying the configuration section to retrieve. Cannot be null or empty.</param>
/// <returns>The <see cref="IConfigurationSection"/> corresponding to the specified key.</returns>
/// <exception cref="InvalidOperationException">Thrown if the configuration root is not initialized or the specified key does not correspond to a valid section.</exception>
private static IConfigurationSection GetSection(string caller)
{
return s_instance?._configRoot.GetSection(caller) ??
throw new InvalidOperationException(caller);
}
private static T LoadSection<T>([CallerMemberName] string? caller = null)
{
if (s_instance is null)
+1
View File
@@ -8,6 +8,7 @@
<IsAotCompatible>false</IsAotCompatible>
<ProjectsTargetFrameworks>net472;net9.0</ProjectsTargetFrameworks>
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
<NoWarn>$(NoWarn);Moq1410;xUnit2023</NoWarn>
</PropertyGroup>
<ItemGroup>
@@ -48,7 +48,7 @@ public class AgentRunResponseTests
{
ChatResponse chatResponse = new()
{
AdditionalProperties = new(),
AdditionalProperties = [],
CreatedAt = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero),
Messages = [new(ChatRole.Assistant, "This is a test message.")],
RawRepresentation = new object(),
@@ -29,7 +29,7 @@ public class AgentRunResponseUpdateTests
{
ChatResponseUpdate chatResponseUpdate = new()
{
AdditionalProperties = new(),
AdditionalProperties = [],
AuthorName = "author",
Contents = [new TextContent("hello")],
ConversationId = "conversationId",
@@ -18,9 +18,9 @@ public class JsonSerializationTests
{
WriteIndented = false, // Use compact JSON for easier testing
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
Converters = { new JsonStringEnumConverter() },
TypeInfoResolver = ActorJsonContext.Default
};
this._options.TypeInfoResolver = ActorJsonContext.Default;
}
#region ActorMessage Tests
@@ -7,6 +7,8 @@ using System.Threading;
using System.Threading.Tasks;
using Moq;
#pragma warning disable RCS1196 // Call extension method as instance method
namespace Microsoft.Extensions.AI.Agents.UnitTests.ChatCompletion;
public class ChatClientAgentExtensionsTests
@@ -24,8 +24,8 @@ public class ChatClientAgentThreadTests
var thread = new ChatClientAgentThread();
// Assert
Assert.IsAssignableFrom<IMessagesRetrievableThread>(thread);
Assert.IsAssignableFrom<AgentThread>(thread);
Assert.IsType<IMessagesRetrievableThread>(thread, exactMatch: false);
Assert.IsType<AgentThread>(thread, exactMatch: false);
}
/// <summary>
@@ -52,9 +52,7 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
// Concatenate the previous messages with the response message to get a full chat history
// that includes the current response.
return previousMessages
.Concat([responseMessage])
.ToList();
return [.. previousMessages, responseMessage];
}
return await chatClientThread.GetMessagesAsync().ToListAsync();