From 0947c6ea77fd36e306184b67f5e1126e2cee8c23 Mon Sep 17 00:00:00 2001 From: Maciej Warchalowski Date: Fri, 24 Apr 2026 02:38:38 +0000 Subject: [PATCH 1/2] Add reasoning option to request chat options in ChatClientAgent --- dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index 44a136da3e..e59ec698b8 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -549,6 +549,7 @@ internal async Task NotifyProvidersOfFailureAsync( requestChatOptions.ModelId ??= this._agentOptions.ChatOptions.ModelId; requestChatOptions.PresencePenalty ??= this._agentOptions.ChatOptions.PresencePenalty; requestChatOptions.ResponseFormat ??= this._agentOptions.ChatOptions.ResponseFormat; + requestChatOptions.Reasoning ??= this._agentOptions.ChatOptions.Reasoning; requestChatOptions.Seed ??= this._agentOptions.ChatOptions.Seed; requestChatOptions.Temperature ??= this._agentOptions.ChatOptions.Temperature; requestChatOptions.TopP ??= this._agentOptions.ChatOptions.TopP; From 317bc283ccf21b1bb9bfb44d2755849f43d0da79 Mon Sep 17 00:00:00 2001 From: Maciej Warchalowski Date: Fri, 24 Apr 2026 03:30:54 +0000 Subject: [PATCH 2/2] Add tests for ChatOptions reasoning merging in ChatClientAgent --- ...ChatClientAgent_ChatOptionsMergingTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatOptionsMergingTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatOptionsMergingTests.cs index e4df863ce0..c4766b83f0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatOptionsMergingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatOptionsMergingTests.cs @@ -347,6 +347,115 @@ public async Task ChatOptionsMergingUsesRawRepresentationFactoryWithFallbackAsyn Assert.Equal(expectedSetting, capturedChatOptions.RawRepresentationFactory(null!)); } + /// + /// Verify that from the request takes priority over the agent's. + /// + [Fact] + public async Task ChatOptionsMergingUsesRequestReasoningOverAgentReasoningAsync() + { + // Arrange + var agentReasoning = new ReasoningOptions { Effort = ReasoningEffort.Low, Output = ReasoningOutput.Full }; + var requestReasoning = new ReasoningOptions { Effort = ReasoningEffort.High, Output = ReasoningOutput.Full }; + + Mock mockService = new(); + ChatOptions? capturedChatOptions = null; + mockService.Setup( + s => s.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => + capturedChatOptions = opts) + .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new ChatOptions { Reasoning = agentReasoning } + }); + var messages = new List { new(ChatRole.User, "test") }; + + // Act + await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions { Reasoning = requestReasoning })); + + // Assert + Assert.NotNull(capturedChatOptions); + Assert.NotNull(capturedChatOptions.Reasoning); + Assert.Equal(requestReasoning.Effort, capturedChatOptions.Reasoning.Effort); + Assert.Equal(requestReasoning.Output, capturedChatOptions.Reasoning.Output); + } + + /// + /// Verify that falls back to the agent's when the request has none. + /// + [Fact] + public async Task ChatOptionsMergingFallsBackToAgentReasoningWhenRequestHasNoneAsync() + { + // Arrange + var agentReasoning = new ReasoningOptions { Effort = ReasoningEffort.Low, Output = ReasoningOutput.Full }; + + Mock mockService = new(); + ChatOptions? capturedChatOptions = null; + mockService.Setup( + s => s.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => + capturedChatOptions = opts) + .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new ChatOptions { Reasoning = agentReasoning } + }); + var messages = new List { new(ChatRole.User, "test") }; + + // Act + await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions())); + + // Assert + Assert.NotNull(capturedChatOptions); + Assert.NotNull(capturedChatOptions.Reasoning); + Assert.Equal(agentReasoning.Effort, capturedChatOptions.Reasoning.Effort); + Assert.Equal(agentReasoning.Output, capturedChatOptions.Reasoning.Output); + } + + /// + /// Verify that from the request is used when the agent has none. + /// + [Fact] + public async Task ChatOptionsMergingUsesRequestReasoningWhenAgentHasNoneAsync() + { + // Arrange + var requestReasoning = new ReasoningOptions { Effort = ReasoningEffort.High, Output = ReasoningOutput.Full }; + + Mock mockService = new(); + ChatOptions? capturedChatOptions = null; + mockService.Setup( + s => s.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => + capturedChatOptions = opts) + .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new ChatOptions() + }); + var messages = new List { new(ChatRole.User, "test") }; + + // Act + await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions { Reasoning = requestReasoning })); + + // Assert + Assert.NotNull(capturedChatOptions); + Assert.NotNull(capturedChatOptions.Reasoning); + Assert.Equal(requestReasoning.Effort, capturedChatOptions.Reasoning.Effort); + Assert.Equal(requestReasoning.Output, capturedChatOptions.Reasoning.Output); + } + /// /// Verify that ChatOptions merging handles all scalar properties correctly. ///