Skip to content

Commit 1e0cd1d

Browse files
authored
.NET: Include ReasoningEncryptedContent by default when stored output disabled with Responses (#4623)
* Include ReasoningEncryptedContent by default when stored output disabled * Fix formatting * Fix formatter
1 parent a51493e commit 1e0cd1d

File tree

5 files changed

+109
-5
lines changed

5 files changed

+109
-5
lines changed

dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,23 @@ public static ChatClientAgent AsAIAgent(
100100
/// This corresponds to setting the "store" property in the JSON representation to false.
101101
/// </remarks>
102102
/// <param name="responseClient">The client.</param>
103+
/// <param name="includeReasoningEncryptedContent">
104+
/// Includes an encrypted version of reasoning tokens in reasoning item outputs.
105+
/// This enables reasoning items to be used in multi-turn conversations when using the Responses API statelessly
106+
/// (like when the store parameter is set to false, or when an organization is enrolled in the zero data retention program).
107+
/// Defaults to <see langword="true"/>.
108+
/// </param>
103109
/// <returns>An <see cref="IChatClient"/> that can be used to converse via the <see cref="ResponsesClient"/> that does not store responses for later retrieval.</returns>
104110
/// <exception cref="ArgumentNullException"><paramref name="responseClient"/> is <see langword="null"/>.</exception>
105111
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
106-
public static IChatClient AsIChatClientWithStoredOutputDisabled(this ResponsesClient responseClient)
112+
public static IChatClient AsIChatClientWithStoredOutputDisabled(this ResponsesClient responseClient, bool includeReasoningEncryptedContent = true)
107113
{
108114
return Throw.IfNull(responseClient)
109115
.AsIChatClient()
110116
.AsBuilder()
111-
.ConfigureOptions(x => x.RawRepresentationFactory = _ => new CreateResponseOptions() { StoredOutputEnabled = false })
117+
.ConfigureOptions(x => x.RawRepresentationFactory = _ => includeReasoningEncryptedContent
118+
? new CreateResponseOptions() { StoredOutputEnabled = false, IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent } }
119+
: new CreateResponseOptions() { StoredOutputEnabled = false })
112120
.Build();
113121
}
114122
}

dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,85 @@ public void AsIChatClientWithStoredOutputDisabled_InnerResponsesClientIsAccessib
291291
Assert.Same(responseClient, innerClient);
292292
}
293293

294+
/// <summary>
295+
/// Verify that AsIChatClientWithStoredOutputDisabled with includeReasoningEncryptedContent false
296+
/// wraps the original ResponsesClient, which remains accessible via the service chain.
297+
/// </summary>
298+
[Fact]
299+
public void AsIChatClientWithStoredOutputDisabled_WithIncludeReasoningFalse_InnerResponsesClientIsAccessible()
300+
{
301+
// Arrange
302+
var responseClient = new TestOpenAIResponseClient();
303+
304+
// Act
305+
var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled(includeReasoningEncryptedContent: false);
306+
307+
// Assert - the inner ResponsesClient should be accessible via GetService
308+
var innerClient = chatClient.GetService<ResponsesClient>();
309+
Assert.NotNull(innerClient);
310+
Assert.Same(responseClient, innerClient);
311+
}
312+
313+
/// <summary>
314+
/// Verify that AsIChatClientWithStoredOutputDisabled with default parameter (includeReasoningEncryptedContent = true)
315+
/// configures StoredOutputEnabled to false and includes ReasoningEncryptedContent in IncludedProperties.
316+
/// </summary>
317+
[Fact]
318+
public void AsIChatClientWithStoredOutputDisabled_Default_ConfiguresStoredOutputDisabledWithReasoningEncryptedContent()
319+
{
320+
// Arrange
321+
var responseClient = new TestOpenAIResponseClient();
322+
323+
// Act
324+
var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled();
325+
326+
// Assert
327+
var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient);
328+
Assert.NotNull(createResponseOptions);
329+
Assert.False(createResponseOptions.StoredOutputEnabled);
330+
Assert.Contains(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
331+
}
332+
333+
/// <summary>
334+
/// Verify that AsIChatClientWithStoredOutputDisabled with includeReasoningEncryptedContent explicitly set to true
335+
/// configures StoredOutputEnabled to false and includes ReasoningEncryptedContent in IncludedProperties.
336+
/// </summary>
337+
[Fact]
338+
public void AsIChatClientWithStoredOutputDisabled_WithIncludeReasoningTrue_ConfiguresStoredOutputDisabledWithReasoningEncryptedContent()
339+
{
340+
// Arrange
341+
var responseClient = new TestOpenAIResponseClient();
342+
343+
// Act
344+
var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled(includeReasoningEncryptedContent: true);
345+
346+
// Assert
347+
var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient);
348+
Assert.NotNull(createResponseOptions);
349+
Assert.False(createResponseOptions.StoredOutputEnabled);
350+
Assert.Contains(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
351+
}
352+
353+
/// <summary>
354+
/// Verify that AsIChatClientWithStoredOutputDisabled with includeReasoningEncryptedContent set to false
355+
/// configures StoredOutputEnabled to false and does not include ReasoningEncryptedContent in IncludedProperties.
356+
/// </summary>
357+
[Fact]
358+
public void AsIChatClientWithStoredOutputDisabled_WithIncludeReasoningFalse_ConfiguresStoredOutputDisabledWithoutReasoningEncryptedContent()
359+
{
360+
// Arrange
361+
var responseClient = new TestOpenAIResponseClient();
362+
363+
// Act
364+
var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled(includeReasoningEncryptedContent: false);
365+
366+
// Assert
367+
var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient);
368+
Assert.NotNull(createResponseOptions);
369+
Assert.False(createResponseOptions.StoredOutputEnabled);
370+
Assert.DoesNotContain(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
371+
}
372+
294373
/// <summary>
295374
/// A simple test IServiceProvider implementation for testing.
296375
/// </summary>
@@ -309,4 +388,24 @@ private sealed class TestServiceProvider : IServiceProvider
309388
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
310389
return property?.GetValue(client) as IServiceProvider;
311390
}
391+
392+
/// <summary>
393+
/// Extracts the <see cref="CreateResponseOptions"/> produced by the ConfigureOptions pipeline
394+
/// by using reflection to access the configure action and invoking it on a test <see cref="ChatOptions"/>.
395+
/// </summary>
396+
private static CreateResponseOptions? GetCreateResponseOptionsFromPipeline(IChatClient chatClient)
397+
{
398+
// The ConfigureOptionsChatClient stores the configure action in a private field.
399+
var configureField = chatClient.GetType().GetField("_configureOptions", BindingFlags.NonPublic | BindingFlags.Instance);
400+
Assert.NotNull(configureField);
401+
402+
var configureAction = configureField.GetValue(chatClient) as Action<ChatOptions>;
403+
Assert.NotNull(configureAction);
404+
405+
var options = new ChatOptions();
406+
configureAction(options);
407+
408+
Assert.NotNull(options.RawRepresentationFactory);
409+
return options.RawRepresentationFactory(chatClient) as CreateResponseOptions;
410+
}
312411
}

dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ChatMessageExtensionsTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3-
using System;
43
using System.Collections.Generic;
54
using System.Linq;
65
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;

dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/EditTableExecutorTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3-
using System;
43
using System.Linq;
54
using System.Threading;
65
using System.Threading.Tasks;

dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3-
using System;
43
using System.Collections.Generic;
54
using System.Threading;
65
using System.Threading.Tasks;

0 commit comments

Comments
 (0)