-
Notifications
You must be signed in to change notification settings - Fork 2.3k
.NET: Add Options for Hosted Agent to Allow Backend Storage #7572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Roger Barreto (rogerbarreto)
merged 11 commits into
microsoft:main
from
rogerbarreto:features/hosted-chat-history-provider-update
Aug 11, 2026
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
3ebc0e8
Let the container choose who stores a hosted turn, and say so when it…
rogerbarreto 4eb4c9c
Rename the stored-session flag to say what it means
rogerbarreto 8de6b5d
Say plainly what server-side storage does to a hosted turn
rogerbarreto fb518e4
Read the store gate as an allow, and align the messages
rogerbarreto 43a7721
Address the review comments left open on the merged PR
rogerbarreto cfba2b3
Address the review on #7572
rogerbarreto 88e388e
Let the chat history provider carry the conversation
rogerbarreto d5b5bb2
Fail a turn to skip its session, and name the store check after what …
rogerbarreto 9165333
Read a hosted response through the agent client, and only forgive a 404
rogerbarreto 98c2011
Move the store setting next to the code that reads and writes it
rogerbarreto ecf45c8
Say that the stored output setting could not be determined, which is …
rogerbarreto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/FoundryResponsesOptions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using Microsoft.Shared.DiagnosticIds; | ||
|
|
||
| namespace Microsoft.Agents.AI.Foundry.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Options for hosting agents behind the Foundry Responses API. | ||
| /// </summary> | ||
| [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] | ||
| public sealed class FoundryResponsesOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets a value indicating whether the agent's own chat client is allowed to store the | ||
| /// responses it produces. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// A hosted turn is already recorded by the storage provider that runs around this handler, and | ||
| /// that record is the conversation the caller reads back. When the service behind the agent's chat | ||
| /// client also stores the turn, the same exchange is written a second time onto a trail of its own, | ||
| /// which nothing here reads and no one reconciles with the first. | ||
| /// </para> | ||
| /// <para> | ||
| /// While this is <see langword="false"/>, hosting turns that storage off for every run (the "store" | ||
| /// property in the JSON representation), and the readiness probe reports an agent whose | ||
| /// configuration would keep it on. Set it to <see langword="true"/> to leave the agent's own | ||
| /// setting exactly as the container configured it, in which case hosting neither changes it nor | ||
| /// checks it. | ||
| /// </para> | ||
| /// </remarks> | ||
| /// <value> | ||
| /// Default is <see langword="false"/>. | ||
| /// </value> | ||
| public bool AllowStoredOutputEnabled { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a value indicating whether to include an encrypted version of reasoning tokens in | ||
| /// reasoning item outputs. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This enables reasoning items to be used in multi-turn conversations when using the Responses API | ||
| /// statelessly (like when the store parameter is set to false, or when an organization is enrolled | ||
| /// in the zero data retention program). It applies only while | ||
| /// <see cref="AllowStoredOutputEnabled"/> is <see langword="false"/>, because that is when hosting | ||
| /// turns storage off and the reasoning items would otherwise be lost between turns. | ||
| /// </remarks> | ||
| /// <value> | ||
| /// Default is <see langword="true"/>. | ||
| /// </value> | ||
| public bool IncludeReasoningEncryptedContent { get; set; } = true; | ||
| } |
127 changes: 127 additions & 0 deletions
127
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedStoredOutputCompatibility.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using Azure.AI.AgentServer.Responses; | ||
| using Azure.AI.AgentServer.Responses.Models; | ||
| using Microsoft.Extensions.AI; | ||
| using ChatCompletionOptions = OpenAI.Chat.ChatCompletionOptions; | ||
| using CreateResponseOptions = OpenAI.Responses.CreateResponseOptions; | ||
| using IncludedResponseProperty = OpenAI.Responses.IncludedResponseProperty; | ||
|
|
||
| namespace Microsoft.Agents.AI.Foundry.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Keeps the service behind a hosted agent's chat client from storing the responses it produces, and | ||
| /// reports the deployment that ends up storing them anyway. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// A hosted turn is already recorded by the AgentServer SDK's storage provider, which runs around the | ||
| /// handler, and that record is the conversation the caller reads back. A service that also stores the | ||
| /// turn writes the same exchange a second time onto a trail of its own, which nothing here reads and | ||
| /// no one reconciles with the first. | ||
| /// </para> | ||
| /// <para> | ||
| /// Turning storage off is a container concern, so a deployment that still stores is a server-side | ||
| /// misconfiguration rather than a bad request, and is reported as such. | ||
| /// </para> | ||
| /// </remarks> | ||
| internal static class HostedStoredOutputCompatibility | ||
| { | ||
| /// <summary> | ||
| /// HTTP status returned when the agent's own service stored the turn. <c>501 Not Implemented</c> | ||
| /// is a server-side classification, because the deployment, not the caller, is misconfigured; it is | ||
| /// also non-retryable and distinct from the generic <c>500</c> so it stands out in telemetry. | ||
| /// </summary> | ||
| internal const int MisconfiguredAgentStatusCode = 501; | ||
|
|
||
| /// <summary> | ||
| /// Stable error code emitted in the response body so callers and tooling can match the condition. | ||
| /// </summary> | ||
| internal const string MisconfiguredAgentErrorCode = "agent_stored_output_not_disabled"; | ||
|
|
||
| /// <summary> | ||
| /// Returns the error to throw when the agent's own service kept the turn. | ||
| /// </summary> | ||
| internal static ResponsesApiException CreateMisconfiguredAgentError() => | ||
| new( | ||
| new Error( | ||
| MisconfiguredAgentErrorCode, | ||
| "The agent should not have server side storage enabled. This produced a new untracked conversation/response in the server while the hosted agent also generated a conversation for the request of the agent. This setting is only allowed when enabling the FoundryResponsesOptions.AllowStoredOutputEnabled flag, which leaves the agent's own storage setting untouched and keeps that second recording on purpose."), | ||
| MisconfiguredAgentStatusCode); | ||
|
|
||
| /// <summary> | ||
| /// Installs a factory on <paramref name="options"/> that turns storage off on the request the agent's | ||
| /// chat client is about to build. | ||
| /// </summary> | ||
| /// <param name="options">The chat options for this run.</param> | ||
| /// <param name="agentRawRepresentationFactory"> | ||
| /// The factory the agent carries on its own <see cref="ChatOptions"/>, if any. It is invoked here and | ||
| /// its result is what gets the setting, because <c>ChatClientAgent</c> chains the two by taking the | ||
| /// agent's only when the request's returns null. A request factory that always answers would | ||
| /// otherwise drop whatever the container configured. | ||
| /// </param> | ||
| /// <param name="includeReasoningEncryptedContent"> | ||
| /// Whether to ask for the encrypted form of the reasoning tokens, which is what keeps reasoning | ||
| /// usable across turns while storage is off. | ||
| /// </param> | ||
| /// <remarks> | ||
| /// Both OpenAI request shapes carry the setting, so a chat client speaking either protocol is | ||
| /// covered. Anything else is a request type with no notion of storing a response, and is handed back | ||
| /// untouched. | ||
| /// </remarks> | ||
| internal static void DisableStoredOutput( | ||
| ChatOptions options, | ||
| Func<IChatClient, object?>? agentRawRepresentationFactory, | ||
| bool includeReasoningEncryptedContent) | ||
| { | ||
| options.RawRepresentationFactory = chatClient => | ||
| { | ||
| switch (agentRawRepresentationFactory?.Invoke(chatClient)) | ||
| { | ||
| case CreateResponseOptions responseOptions: | ||
| return DisableStoredOutput(responseOptions, includeReasoningEncryptedContent); | ||
|
|
||
| case ChatCompletionOptions completionOptions: | ||
| completionOptions.StoredOutputEnabled = false; | ||
| return completionOptions; | ||
|
|
||
| case { } configuredByTheAgent: | ||
| return configuredByTheAgent; | ||
|
|
||
| default: | ||
| return DisableStoredOutput(new CreateResponseOptions(), includeReasoningEncryptedContent); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Reads whether a request the agent's chat client would send asks for the response to be stored. | ||
| /// Returns <see langword="null"/> when the request shape carries no such setting, which is a request | ||
| /// type this package has nothing to say about. | ||
| /// </summary> | ||
| internal static bool? ReadsAsStoringResponses(object? rawRepresentation) => rawRepresentation switch | ||
| { | ||
| CreateResponseOptions responseOptions => responseOptions.StoredOutputEnabled, | ||
| ChatCompletionOptions completionOptions => completionOptions.StoredOutputEnabled, | ||
| _ => null, | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Turns storage off on a Responses request, and keeps reasoning usable across turns while it is off | ||
| /// by asking for the encrypted form of the reasoning tokens. Mirrors what | ||
| /// <c>AsIChatClientWithStoredOutputDisabled</c> does. | ||
| /// </summary> | ||
| private static CreateResponseOptions DisableStoredOutput(CreateResponseOptions responseOptions, bool includeReasoningEncryptedContent) | ||
| { | ||
| responseOptions.StoredOutputEnabled = false; | ||
|
|
||
| if (includeReasoningEncryptedContent && | ||
| !responseOptions.IncludedProperties.Contains(IncludedResponseProperty.ReasoningEncryptedContent)) | ||
| { | ||
| responseOptions.IncludedProperties.Add(IncludedResponseProperty.ReasoningEncryptedContent); | ||
| } | ||
|
|
||
| return responseOptions; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.