Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
678a22a
add support for background responses
SergeyMenshykh Sep 25, 2025
f2afc33
merge with lates main
SergeyMenshykh Sep 26, 2025
61c4434
Update src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesCh…
SergeyMenshykh Sep 27, 2025
f41d7b2
clone chat options
SergeyMenshykh Sep 27, 2025
09db9fc
use readonlymemory instead of array of bytes
SergeyMenshykh Sep 27, 2025
a5b4967
Update src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesCo…
SergeyMenshykh Sep 27, 2025
25e610e
throw exception if no response id provided in continuation token
SergeyMenshykh Sep 27, 2025
611d704
disable S104 rule and throw argument exception via the throw utility.
SergeyMenshykh Sep 27, 2025
fba243b
Merge branch 'background-responses' of https://github.com/SergeyMensh…
SergeyMenshykh Sep 27, 2025
51a4e6c
fix failed unit test
SergeyMenshykh Sep 27, 2025
b1e1a9e
addressing PR review comments
SergeyMenshykh Sep 29, 2025
a384e94
mark new properties as experimantal and json ignore to prevent warnin…
SergeyMenshykh Sep 29, 2025
30b4c0a
make resumption token and its converter experimental
SergeyMenshykh Sep 30, 2025
d68b4a4
Merge branch 'main' into background-responses
SergeyMenshykh Sep 30, 2025
21b04db
remove copy constructor of background response options
SergeyMenshykh Sep 30, 2025
991789e
Merge branch 'background-responses' of https://github.com/SergeyMensh…
SergeyMenshykh Sep 30, 2025
78eef28
rename resumption token
SergeyMenshykh Sep 30, 2025
090667e
rename leftovers
SergeyMenshykh Sep 30, 2025
100f6da
fix unit tests
SergeyMenshykh Sep 30, 2025
77832ea
Merge branch 'main' into background-responses
SergeyMenshykh Sep 30, 2025
b87472a
Merge branch 'main' into background-responses
SergeyMenshykh Oct 1, 2025
8cdd78f
Merge branch 'main' into background-responses
SergeyMenshykh Oct 1, 2025
0d6af71
Update src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatComplet…
SergeyMenshykh Oct 2, 2025
da996e8
Update src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatComplet…
SergeyMenshykh Oct 2, 2025
5d3b6d3
Update src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatComplet…
SergeyMenshykh Oct 2, 2025
57ca6fd
add a warning if different options are provided, and remove the unnec…
SergeyMenshykh Oct 2, 2025
8c931a4
Merge branch 'background-responses' of https://github.com/SergeyMensh…
SergeyMenshykh Oct 2, 2025
8829e8d
register ResponseContinuationToken for source generation
SergeyMenshykh Oct 2, 2025
c2fcc5a
merge with latest main
SergeyMenshykh Oct 6, 2025
f0543fd
Merge branch 'main' into background-responses
SergeyMenshykh Oct 7, 2025
3f4188d
Merge branch 'main' into background-responses
stephentoub Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Libraries/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2936,7 +2936,7 @@ dotnet_diagnostic.S103.severity = suggestion
# Title : Files should not have too many lines of code
# Category : Major Code Smell
# Help Link: https://rules.sonarsource.com/csharp/RSPEC-104
dotnet_diagnostic.S104.severity = warning
dotnet_diagnostic.S104.severity = none

# Title : Finalizers should not throw exceptions
# Category : Blocker Bug
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Extensions.AI;

/// <summary>Options for configuring background responses.</summary>
[Experimental("MEAI001")]
public sealed class BackgroundResponsesOptions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What were the other options again that we were thinking we may need to add here in future to justify a separate options object?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One that I can think of at the moment would be settings for polling if we ever decide to do internal polling. However, if we agree to offload polling responsibility to consumers, and if there are no other options we can think of, we can remove the class and go with the bool? ChatOptions.AllowBackgroundResponses property instead. That would simplify the API a little bit from:

var chatOptions = new ChatOptions
{
    BackgroundResponsesOptions = new() 
    { 
        Allow = true
    }
};

var response = await ChatClient.GetResponseAsync("<prompt>", chatOptions);

to

var chatOptions = new ChatOptions
{
    AllowBackgroundResponses = true
};

var response = await ChatClient.GetResponseAsync("<prompt>", chatOptions);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove the class and go with the bool? ChatOptions.AllowBackgroundResponses property instead

Let's do that.

{
/// <summary>Gets or sets a value indicating whether the background responses are allowed.</summary>
/// <remarks>
/// <para>
/// Background responses allow running long-running operations or tasks asynchronously in the background that can be resumed by streaming APIs
/// and polled for completion by non-streaming APIs.
/// </para>
/// <para>
/// When this property is set to true, non-streaming APIs start a background operation and return an initial
/// response with a continuation token. Subsequent calls to the same API should be made in a polling manner with
/// the continuation token to get the final result of the operation.
/// </para>
/// <para>
/// When this property is set to true, streaming APIs also start a background operation and begin streaming
/// response updates until the operation is completed. If the streaming connection is interrupted, the
/// continuation token obtained from the last update should be supplied to a subsequent call to the same streaming API
/// to resume the stream from the point of interruption and continue receiving updates until the operation is completed.
/// </para>
/// <para>
/// This property only takes effect if the API it's used with supports background responses.
/// If the API does not support background responses, this property will be ignored.
/// </para>
/// </remarks>
public bool? Allow { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
- Added protected copy constructors to options types (e.g. `ChatOptions`).
- Fixed `EmbeddingGeneratorOptions`/`SpeechToTextOptions` `Clone` methods to correctly copy all properties.
- Fixed `ToChatResponse` to not overwrite `ChatMessage/ChatResponse.CreatedAt` with older timestamps during coalescing.
- Added `[Experimental]` `ResponseContinuationToken` to represent a base class for continuation tokens.
- Added `[Experimental]` `BackgroundResponsesOptions` to represent options for background responses.
- Added `[Experimental]` `ChatOptions.BackgroundResponsesOptions` and `ChatOptions.ContinuationToken` properties to accept options for background responses and continuation token.
- Added `[Experimental]` `ChatResponse.ContinuationToken` and `ChatResponseUpdate.ContinuationToken` to return continuation token for background responses.
Comment on lines +8 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Added `[Experimental]` `ResponseContinuationToken` to represent a base class for continuation tokens.
- Added `[Experimental]` `BackgroundResponsesOptions` to represent options for background responses.
- Added `[Experimental]` `ChatOptions.BackgroundResponsesOptions` and `ChatOptions.ContinuationToken` properties to accept options for background responses and continuation token.
- Added `[Experimental]` `ChatResponse.ContinuationToken` and `ChatResponseUpdate.ContinuationToken` to return continuation token for background responses.
- Added `[Experimental]` support for background responses, such that non-streaming responses are allowed to be pollable, and such that responses and response updates can be tagged with continuation tokens to support later resumption.

- Added `[Experimental]` `GetResponseAsync` and `GetStreamingResponseAsync` extension methods for `IChatClient` to continue background responses.

## 9.9.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
Expand Down Expand Up @@ -120,6 +121,34 @@ public static Task<ChatResponse> GetResponseAsync(
return client.GetResponseAsync([chatMessage], options, cancellationToken);
}

/// <summary>Gets a background response identified by the specified continuation token.</summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <summary>Gets a background response identified by the specified continuation token.</summary>
/// <summary>Gets a previously-submitted background response identified by the specified continuation token.</summary>

/// <param name="client">The chat client.</param>
/// <param name="continuationToken">The continuation token.</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <param name="continuationToken">The continuation token.</param>
/// <param name="continuationToken">The continuation token provided with the previous response.</param>

/// <param name="options">The chat options to configure the request.</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <param name="options">The chat options to configure the request.</param>
/// <param name="options">The chat options with which to configure the request.</param>

/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The background response.</returns>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on what "background response" means? And is it necessarily a "background" response? Does the abstraction contract say that a continuation token is only ever provided if AllowBackground is true?

/// <remarks>
/// The options provided should be the same as those used to start the background operation.
/// Using different options is not supported and may lead to unexpected results.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="continuationToken"/> is <see langword="null"/>.</exception>
[Experimental("MEAI001")]
public static Task<ChatResponse> GetResponseAsync(
this IChatClient client,
ResponseContinuationToken continuationToken,
ChatOptions? options = null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of the options being accepted here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. We need to supply functions for subsequent calls if they were advertised in the initial call; otherwise, the called tools won't be found by FICC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some remarks here to warn users that the options should match the original options, otherwise you may have unexpected results and any such action is not supported?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea - added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to supply functions for subsequent calls if they were advertised in the initial call; otherwise, the called tools won't be found by FICC.

The remarks should be more explicit about this. Even after reading the remarks, I didn't understand why this was necessary.

CancellationToken cancellationToken = default)
{
_ = Throw.IfNull(client);
_ = Throw.IfNull(continuationToken);

ChatOptions chatOptions = options?.Clone() ?? new();
chatOptions.ContinuationToken = continuationToken;

return client.GetResponseAsync([], chatOptions, cancellationToken);
}

/// <summary>Sends a user chat text message and streams the response messages.</summary>
/// <param name="client">The chat client.</param>
/// <param name="chatMessage">The text content for the chat message to send.</param>
Expand Down Expand Up @@ -159,4 +188,28 @@ public static IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(

return client.GetStreamingResponseAsync([chatMessage], options, cancellationToken);
}

/// <summary>Gets a background streamed response identified by the specified continuation token and streams its messages.</summary>
/// <param name="client">The chat client.</param>
/// <param name="continuationToken">The continuation token.</param>
/// <param name="options">The chat options to configure the request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The background response messages.</returns>
/// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="continuationToken"/> is <see langword="null"/>.</exception>
[Experimental("MEAI001")]
public static IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
this IChatClient client,
ResponseContinuationToken continuationToken,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
_ = Throw.IfNull(client);
_ = Throw.IfNull(continuationToken);

ChatOptions chatOptions = options?.Clone() ?? new();
chatOptions.ContinuationToken = continuationToken;

return client.GetStreamingResponseAsync([], chatOptions, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

namespace Microsoft.Extensions.AI;
Expand All @@ -27,6 +28,7 @@ protected ChatOptions(ChatOptions? other)
AdditionalProperties = other.AdditionalProperties?.Clone();
AllowMultipleToolCalls = other.AllowMultipleToolCalls;
ConversationId = other.ConversationId;
ContinuationToken = other.ContinuationToken;
FrequencyPenalty = other.FrequencyPenalty;
Instructions = other.Instructions;
MaxOutputTokens = other.MaxOutputTokens;
Expand All @@ -49,6 +51,14 @@ protected ChatOptions(ChatOptions? other)
{
Tools = [.. other.Tools];
}

if (other.BackgroundResponsesOptions is not null)
{
BackgroundResponsesOptions = new BackgroundResponsesOptions
{
Allow = other.BackgroundResponsesOptions.Allow
};
}
}

/// <summary>Gets or sets an optional identifier used to associate a request with an existing conversation.</summary>
Expand Down Expand Up @@ -155,6 +165,26 @@ protected ChatOptions(ChatOptions? other)
[JsonIgnore]
public IList<AITool>? Tools { get; set; }

/// <summary>Gets or sets the options for configuring background responses.</summary>
[Experimental("MEAI001")]
[JsonIgnore]
public BackgroundResponsesOptions? BackgroundResponsesOptions { get; set; }

/// <summary>Gets or sets the continuation token for resuming and getting the result of the chat response identified by this token.</summary>
/// <remarks>
/// This property is used for background responses that can be activated via the <see cref="BackgroundResponsesOptions.Allow"/>
/// property if the <see cref="IChatClient"/> implementation supports them.
/// Streamed background responses, such as those returned by default by <see cref="IChatClient.GetStreamingResponseAsync"/>,
/// can be resumed if interrupted. This means that a continuation token obtained from the <see cref="ChatResponseUpdate.ContinuationToken"/>
/// of an update just before the interruption occurred can be passed to this property to resume the stream from the point of interruption.
/// Non-streamed background responses, such as those returned by <see cref="IChatClient.GetResponseAsync"/>,
/// can be polled for completion by obtaining the token from the <see cref="ChatResponse.ContinuationToken"/> property
/// and passing it to this property on subsequent calls to <see cref="IChatClient.GetResponseAsync"/>.
/// </remarks>
[Experimental("MEAI001")]
[JsonIgnore]
public ResponseContinuationToken? ContinuationToken { get; set; }

/// <summary>
/// Gets or sets a callback responsible for creating the raw representation of the chat options from an underlying implementation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ public IList<ChatMessage> Messages
/// <summary>Gets or sets usage details for the chat response.</summary>
public UsageDetails? Usage { get; set; }

/// <summary>Gets or sets the continuation token for getting result of the background chat response.</summary>
/// <remarks>
/// <see cref="IChatClient"/> implementations that support background responses will return
/// a continuation token if background responses are allowed in <see cref="ChatOptions.BackgroundResponsesOptions"/>
/// and the result of the response has not been obtained yet. Otherwise, the token will be <see langword="null"/>.
/// <para>
/// This property should be used in conjunction with <see cref="ChatOptions.ContinuationToken"/> to
/// continue to poll for the completion of the response. Pass this token to
/// <see cref="ChatOptions.ContinuationToken"/> on subsequent calls to <see cref="IChatClient.GetResponseAsync"/>
/// to poll for completion.
/// </para>
/// </remarks>
[Experimental("MEAI001")]
[JsonIgnore]
public ResponseContinuationToken? ContinuationToken { get; set; }

/// <summary>Gets or sets the raw representation of the chat response from an underlying implementation.</summary>
/// <remarks>
/// If a <see cref="ChatResponse"/> is created to represent some underlying object from another object
Expand Down Expand Up @@ -143,6 +159,7 @@ public ChatResponseUpdate[] ToChatResponseUpdates()
ResponseId = ResponseId,

CreatedAt = message.CreatedAt ?? CreatedAt,
ContinuationToken = ContinuationToken,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ public IList<AIContent> Contents
/// <inheritdoc/>
public override string ToString() => Text;

/// <summary>Gets or sets the continuation token for resuming the streamed chat response of which this update is a part.</summary>
/// <remarks>
/// <see cref="IChatClient"/> implementations that support background responses will return
/// a continuation token on each update if background responses are enabled in <see cref="ChatOptions.BackgroundResponsesOptions"/>
/// and not all updates for the response have been streamed yet. Otherwise, the token will be <see langword="null"/>.
/// <para>
/// This property should be used for stream resumption, where the continuation token of the latest received update should be
/// passed to <see cref="ChatOptions.ContinuationToken"/> on subsequent calls to <see cref="IChatClient.GetStreamingResponseAsync"/>
/// to resume streaming from the point of interruption.
/// </para>
/// </remarks>
[JsonIgnore]
[Experimental("MEAI001")]
public ResponseContinuationToken? ContinuationToken { get; set; }

/// <summary>Gets a <see cref="AIContent"/> object to display in the debugger display.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private AIContent? ContentForDebuggerDisplay
Expand Down
Loading
Loading