Skip to content

Commit 9486c76

Browse files
.NET: Add Reasoning to ChatClientAgent ChatOptions merging (microsoft#5463)
* Add reasoning option to request chat options in ChatClientAgent * Add tests for ChatOptions reasoning merging in ChatClientAgent --------- Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
1 parent caa75f7 commit 9486c76

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ internal async Task NotifyProvidersOfFailureAsync(
564564
requestChatOptions.ModelId ??= this._agentOptions.ChatOptions.ModelId;
565565
requestChatOptions.PresencePenalty ??= this._agentOptions.ChatOptions.PresencePenalty;
566566
requestChatOptions.ResponseFormat ??= this._agentOptions.ChatOptions.ResponseFormat;
567+
requestChatOptions.Reasoning ??= this._agentOptions.ChatOptions.Reasoning;
567568
requestChatOptions.Seed ??= this._agentOptions.ChatOptions.Seed;
568569
requestChatOptions.Temperature ??= this._agentOptions.ChatOptions.Temperature;
569570
requestChatOptions.TopP ??= this._agentOptions.ChatOptions.TopP;

dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatOptionsMergingTests.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,115 @@ public async Task ChatOptionsMergingUsesRawRepresentationFactoryWithFallbackAsyn
347347
Assert.Equal(expectedSetting, capturedChatOptions.RawRepresentationFactory(null!));
348348
}
349349

350+
/// <summary>
351+
/// Verify that <see cref="ChatOptions.Reasoning"/> from the request takes priority over the agent's.
352+
/// </summary>
353+
[Fact]
354+
public async Task ChatOptionsMergingUsesRequestReasoningOverAgentReasoningAsync()
355+
{
356+
// Arrange
357+
var agentReasoning = new ReasoningOptions { Effort = ReasoningEffort.Low, Output = ReasoningOutput.Full };
358+
var requestReasoning = new ReasoningOptions { Effort = ReasoningEffort.High, Output = ReasoningOutput.Full };
359+
360+
Mock<IChatClient> mockService = new();
361+
ChatOptions? capturedChatOptions = null;
362+
mockService.Setup(
363+
s => s.GetResponseAsync(
364+
It.IsAny<IEnumerable<ChatMessage>>(),
365+
It.IsAny<ChatOptions>(),
366+
It.IsAny<CancellationToken>()))
367+
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) =>
368+
capturedChatOptions = opts)
369+
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
370+
371+
ChatClientAgent agent = new(mockService.Object, options: new()
372+
{
373+
ChatOptions = new ChatOptions { Reasoning = agentReasoning }
374+
});
375+
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
376+
377+
// Act
378+
await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions { Reasoning = requestReasoning }));
379+
380+
// Assert
381+
Assert.NotNull(capturedChatOptions);
382+
Assert.NotNull(capturedChatOptions.Reasoning);
383+
Assert.Equal(requestReasoning.Effort, capturedChatOptions.Reasoning.Effort);
384+
Assert.Equal(requestReasoning.Output, capturedChatOptions.Reasoning.Output);
385+
}
386+
387+
/// <summary>
388+
/// Verify that <see cref="ChatOptions.Reasoning"/> falls back to the agent's when the request has none.
389+
/// </summary>
390+
[Fact]
391+
public async Task ChatOptionsMergingFallsBackToAgentReasoningWhenRequestHasNoneAsync()
392+
{
393+
// Arrange
394+
var agentReasoning = new ReasoningOptions { Effort = ReasoningEffort.Low, Output = ReasoningOutput.Full };
395+
396+
Mock<IChatClient> mockService = new();
397+
ChatOptions? capturedChatOptions = null;
398+
mockService.Setup(
399+
s => s.GetResponseAsync(
400+
It.IsAny<IEnumerable<ChatMessage>>(),
401+
It.IsAny<ChatOptions>(),
402+
It.IsAny<CancellationToken>()))
403+
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) =>
404+
capturedChatOptions = opts)
405+
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
406+
407+
ChatClientAgent agent = new(mockService.Object, options: new()
408+
{
409+
ChatOptions = new ChatOptions { Reasoning = agentReasoning }
410+
});
411+
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
412+
413+
// Act
414+
await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions()));
415+
416+
// Assert
417+
Assert.NotNull(capturedChatOptions);
418+
Assert.NotNull(capturedChatOptions.Reasoning);
419+
Assert.Equal(agentReasoning.Effort, capturedChatOptions.Reasoning.Effort);
420+
Assert.Equal(agentReasoning.Output, capturedChatOptions.Reasoning.Output);
421+
}
422+
423+
/// <summary>
424+
/// Verify that <see cref="ChatOptions.Reasoning"/> from the request is used when the agent has none.
425+
/// </summary>
426+
[Fact]
427+
public async Task ChatOptionsMergingUsesRequestReasoningWhenAgentHasNoneAsync()
428+
{
429+
// Arrange
430+
var requestReasoning = new ReasoningOptions { Effort = ReasoningEffort.High, Output = ReasoningOutput.Full };
431+
432+
Mock<IChatClient> mockService = new();
433+
ChatOptions? capturedChatOptions = null;
434+
mockService.Setup(
435+
s => s.GetResponseAsync(
436+
It.IsAny<IEnumerable<ChatMessage>>(),
437+
It.IsAny<ChatOptions>(),
438+
It.IsAny<CancellationToken>()))
439+
.Callback<IEnumerable<ChatMessage>, ChatOptions, CancellationToken>((msgs, opts, ct) =>
440+
capturedChatOptions = opts)
441+
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
442+
443+
ChatClientAgent agent = new(mockService.Object, options: new()
444+
{
445+
ChatOptions = new ChatOptions()
446+
});
447+
var messages = new List<ChatMessage> { new(ChatRole.User, "test") };
448+
449+
// Act
450+
await agent.RunAsync(messages, options: new ChatClientAgentRunOptions(new ChatOptions { Reasoning = requestReasoning }));
451+
452+
// Assert
453+
Assert.NotNull(capturedChatOptions);
454+
Assert.NotNull(capturedChatOptions.Reasoning);
455+
Assert.Equal(requestReasoning.Effort, capturedChatOptions.Reasoning.Effort);
456+
Assert.Equal(requestReasoning.Output, capturedChatOptions.Reasoning.Output);
457+
}
458+
350459
/// <summary>
351460
/// Verify that ChatOptions merging handles all scalar properties correctly.
352461
/// </summary>

0 commit comments

Comments
 (0)