From 9faa27b8ebcfa500d05363dbcb381cd500a7b8aa Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:55:44 +0100 Subject: [PATCH] .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> --- dotnet/.editorconfig | 6 ++++ ...atClientAgent_UsingCodeInterpreterTools.cs | 31 ++++++++----------- ...p04_ChatClientAgent_DependencyInjection.cs | 2 +- ...tep06_ChatClientAgent_StructuredOutputs.cs | 16 +++++----- ...07_ChatClientAgent_UsingFileSearchTools.cs | 15 +++------ .../HelloHttpApi.Web/AgentClient.cs | 2 +- .../NullableAttributes.cs | 2 +- .../DynamicallyAccessedMemberTypes.cs | 2 ++ .../ConcurrentOrchestration.cs | 17 ++++++---- .../NewPersistentAgentsChatClient.cs | 2 ++ .../ChatCompletion/ChatClientAgent.cs | 5 ++- .../src/Shared/Samples/TestConfiguration.cs | 17 ---------- dotnet/tests/Directory.Build.props | 1 + .../AgentRunResponseTests.cs | 2 +- .../AgentRunResponseUpdatesTests.cs | 2 +- .../JsonSerializationTests.cs | 4 +-- .../ChatClientAgentExtensionsTests.cs | 2 ++ .../ChatClientAgentThreadTests.cs | 4 +-- .../OpenAIResponseFixture.cs | 4 +-- 19 files changed, 63 insertions(+), 73 deletions(-) diff --git a/dotnet/.editorconfig b/dotnet/.editorconfig index 75b647fee3..76f90b6f33 100644 --- a/dotnet/.editorconfig +++ b/dotnet/.editorconfig @@ -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) diff --git a/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs b/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs index a968ac6ef7..ea4c3ed8da 100644 --- a/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step03_ChatClientAgent_UsingCodeInterpreterTools.cs @@ -105,26 +105,21 @@ public sealed class Step03_ChatClientAgent_UsingCodeInterpreterTools(ITestOutput /// Provider of the chat client that is used to determine how to extract the output. /// The code interpreter output as a string. 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().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().SelectMany(l => l.Logs) + )}", + _ => null, + }; #endregion } diff --git a/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs b/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs index a793adf3c4..45c9f57342 100644 --- a/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs +++ b/dotnet/samples/GettingStarted/Steps/Step04_ChatClientAgent_DependencyInjection.cs @@ -40,7 +40,7 @@ public sealed class Step04_ChatClientAgent_DependencyInjection(ITestOutputHelper loggerFactory: sp.GetRequiredService())); // 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(); diff --git a/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs b/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs index c49c081316..059086aa2a 100644 --- a/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs +++ b/dotnet/samples/GettingStarted/Steps/Step06_ChatClientAgent_StructuredOutputs.cs @@ -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). diff --git a/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs b/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs index a4fcf2570e..d00b1bdb64 100644 --- a/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step07_ChatClientAgent_UsingFileSearchTools.cs @@ -100,17 +100,12 @@ public sealed class Step07_ChatClientAgent_UsingFileSearchTools(ITestOutputHelpe } private Task CreateVectorStoreAsync(IEnumerable 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 CreateVectorStoreOpenAIAssistantAsync(IEnumerable fileIds) { diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.Web/AgentClient.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.Web/AgentClient.cs index 7761cfdc31..8a17efbb32 100644 --- a/dotnet/samples/HelloHttpApi/HelloHttpApi.Web/AgentClient.cs +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.Web/AgentClient.cs @@ -33,7 +33,7 @@ public class AgentClient(HttpClient httpClient, ILogger 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; diff --git a/dotnet/src/LegacySupport/DiagnosticAttributes/NullableAttributes.cs b/dotnet/src/LegacySupport/DiagnosticAttributes/NullableAttributes.cs index 393f24d58b..3f1baec540 100644 --- a/dotnet/src/LegacySupport/DiagnosticAttributes/NullableAttributes.cs +++ b/dotnet/src/LegacySupport/DiagnosticAttributes/NullableAttributes.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -#pragma warning disable CA1019 +#pragma warning disable CA1019, RCS1251, IDE0300 namespace System.Diagnostics.CodeAnalysis; diff --git a/dotnet/src/LegacySupport/TrimAttributes/DynamicallyAccessedMemberTypes.cs b/dotnet/src/LegacySupport/TrimAttributes/DynamicallyAccessedMemberTypes.cs index c8663103a5..8f756b8c4b 100644 --- a/dotnet/src/LegacySupport/TrimAttributes/DynamicallyAccessedMemberTypes.cs +++ b/dotnet/src/LegacySupport/TrimAttributes/DynamicallyAccessedMemberTypes.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +#pragma warning disable RCS1157 // Composite enum value contains undefined flag + namespace System.Diagnostics.CodeAnalysis; /// diff --git a/dotnet/src/Microsoft.Agents.Orchestration/ConcurrentOrchestration.cs b/dotnet/src/Microsoft.Agents.Orchestration/ConcurrentOrchestration.cs index a28c203db3..670731efe8 100644 --- a/dotnet/src/Microsoft.Agents.Orchestration/ConcurrentOrchestration.cs +++ b/dotnet/src/Microsoft.Agents.Orchestration/ConcurrentOrchestration.cs @@ -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; } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs index 5507171516..59ff21fbd3 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.AzureAI/NewPersistentAgentsChatClient.cs @@ -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 || diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs b/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs index 1d0616e0f6..a664e214e0 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents/ChatCompletion/ChatClientAgent.cs @@ -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 ?? chatResponse.Messages.ToArray(); + var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection ?? [.. 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 ?? chatResponse.Messages.ToArray(); + var chatResponseMessages = chatResponse.Messages as IReadOnlyCollection ?? [.. 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. diff --git a/dotnet/src/Shared/Samples/TestConfiguration.cs b/dotnet/src/Shared/Samples/TestConfiguration.cs index 89a1404d2d..be436ee9f7 100644 --- a/dotnet/src/Shared/Samples/TestConfiguration.cs +++ b/dotnet/src/Shared/Samples/TestConfiguration.cs @@ -72,28 +72,11 @@ public sealed class TestConfiguration this._configRoot = configRoot; } - /// - /// Provides access to the configuration root for the application. - /// - private static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot; - /// /// Gets the configuration settings for the AzureAI integration. /// public static AzureAIConfig AzureAI => LoadSection(); - /// - /// Retrieves a configuration section based on the specified key. - /// - /// The key identifying the configuration section to retrieve. Cannot be null or empty. - /// The corresponding to the specified key. - /// Thrown if the configuration root is not initialized or the specified key does not correspond to a valid section. - private static IConfigurationSection GetSection(string caller) - { - return s_instance?._configRoot.GetSection(caller) ?? - throw new InvalidOperationException(caller); - } - private static T LoadSection([CallerMemberName] string? caller = null) { if (s_instance is null) diff --git a/dotnet/tests/Directory.Build.props b/dotnet/tests/Directory.Build.props index 3a8ad53b48..9fb9d9b797 100644 --- a/dotnet/tests/Directory.Build.props +++ b/dotnet/tests/Directory.Build.props @@ -8,6 +8,7 @@ false net472;net9.0 b7762d10-e29b-4bb1-8b74-b6d69a667dd4 + $(NoWarn);Moq1410;xUnit2023 diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseTests.cs index 1ef375901b..46610d7138 100644 --- a/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseTests.cs +++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseTests.cs @@ -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(), diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseUpdatesTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseUpdatesTests.cs index 55596ffe72..ae8fdb1da0 100644 --- a/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseUpdatesTests.cs +++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.Abstractions.UnitTests/AgentRunResponseUpdatesTests.cs @@ -29,7 +29,7 @@ public class AgentRunResponseUpdateTests { ChatResponseUpdate chatResponseUpdate = new() { - AdditionalProperties = new(), + AdditionalProperties = [], AuthorName = "author", Contents = [new TextContent("hello")], ConversationId = "conversationId", diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests/JsonSerializationTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests/JsonSerializationTests.cs index efd59c2987..78b71a833e 100644 --- a/dotnet/tests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests/JsonSerializationTests.cs +++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.Runtime.Abstractions.UnitTests/JsonSerializationTests.cs @@ -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 diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentExtensionsTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentExtensionsTests.cs index 1152e6da02..52ec5f74f2 100644 --- a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentExtensionsTests.cs @@ -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 diff --git a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs index 07ed61ceb0..9fd357eea0 100644 --- a/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs +++ b/dotnet/tests/Microsoft.Extensions.AI.Agents.UnitTests/ChatCompletion/ChatClientAgentThreadTests.cs @@ -24,8 +24,8 @@ public class ChatClientAgentThreadTests var thread = new ChatClientAgentThread(); // Assert - Assert.IsAssignableFrom(thread); - Assert.IsAssignableFrom(thread); + Assert.IsType(thread, exactMatch: false); + Assert.IsType(thread, exactMatch: false); } /// diff --git a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs index 6a0a24c7d4..3acb3b3cb5 100644 --- a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs +++ b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs @@ -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();