Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7226300
add support for background responses
SergeyMenshykh Oct 15, 2025
1e2a0a4
Merge branch 'main' into add-background-responses
SergeyMenshykh Oct 15, 2025
6f9d678
Update dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponseUp…
SergeyMenshykh Oct 15, 2025
2bee1c3
fix broken link
SergeyMenshykh Oct 15, 2025
14f5b87
Merge branch 'add-background-responses' of https://github.com/SergeyM…
SergeyMenshykh Oct 15, 2025
0f1a799
fix xml comments and background responses properties override funcito…
SergeyMenshykh Oct 15, 2025
a8cd337
change ai model provider
SergeyMenshykh Oct 16, 2025
99c6bb5
Merge branch 'main' into add-background-responses
SergeyMenshykh Oct 16, 2025
f1e2105
use Run{Streaming}Async overloads that don't require messages
SergeyMenshykh Oct 16, 2025
190e132
Merge branch 'add-background-responses' of https://github.com/SergeyM…
SergeyMenshykh Oct 16, 2025
ade1519
stop using m: prefix in cref attribute of <see/> element.
SergeyMenshykh Oct 16, 2025
955d926
reject input messages provided with continuation token + don't extrac…
SergeyMenshykh Oct 17, 2025
a9665ad
use agent thread for background-responses sample
SergeyMenshykh Oct 17, 2025
a61ca04
require agent thread for background responses
SergeyMenshykh Oct 20, 2025
0507e3f
Merge branch 'main' into add-background-responses
SergeyMenshykh Oct 20, 2025
f338305
Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
SergeyMenshykh Oct 21, 2025
8a053a0
Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
SergeyMenshykh Oct 21, 2025
dacada8
remove CA1200
SergeyMenshykh Oct 21, 2025
7b1cba7
Merge branch 'add-background-responses' of https://github.com/SergeyM…
SergeyMenshykh Oct 21, 2025
53d0288
Update dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs
SergeyMenshykh Oct 22, 2025
ce4c9d8
Update dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
SergeyMenshykh Oct 22, 2025
dc63bdd
Update dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
SergeyMenshykh Oct 22, 2025
a1166cb
address pr review comments
SergeyMenshykh Oct 22, 2025
89637ef
Merge branch 'add-background-responses' of https://github.com/SergeyM…
SergeyMenshykh Oct 22, 2025
b254cbb
Merge branch 'main' into add-background-responses
SergeyMenshykh Oct 22, 2025
a502ce7
Update dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundRe…
SergeyMenshykh Oct 22, 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
1,660 changes: 1,660 additions & 0 deletions docs/decisions/0009-support-long-running-operations.md

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</Folder>
<Folder Name="/Samples/GettingStarted/A2A/">
<File Path="samples/GettingStarted/A2A/README.md" />
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj"/>
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentProviders/">
<File Path="samples/GettingStarted/AgentProviders/README.md" />
Expand Down Expand Up @@ -57,6 +57,7 @@
<Project Path="samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithOpenAI/">
<File Path="samples/GettingStarted/AgentWithOpenAI/README.md" />
Expand Down Expand Up @@ -159,8 +160,14 @@
<Folder Name="/Solution Items/docs/" />
<Folder Name="/Solution Items/docs/decisions/">
<File Path="../docs/decisions/0001-agent-run-response.md" />
<File Path="../docs/decisions/0001-agent-tools.md" />
<File Path="../docs/decisions/0002-agent-opentelemetry-instrumentation.md" />
<File Path="../docs/decisions/0002-agent-tools.md" />
<File Path="../docs/decisions/0003-agent-opentelemetry-instrumentation.md" />
<File Path="../docs/decisions/0004-foundry-sdk-extensions.md" />
<File Path="../docs/decisions/0005-python-naming-conventions.md" />
<File Path="../docs/decisions/0006-userapproval.md" />
<File Path="../docs/decisions/0007-agent-filtering-middleware.md" />
<File Path="../docs/decisions/0008-python-subpackages.md" />
<File Path="../docs/decisions/0009-support-long-running-operations.md" />
<File Path="../docs/decisions/adr-short-template.md" />
<File Path="../docs/decisions/adr-template.md" />
<File Path="../docs/decisions/README.md" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>

<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.

// This sample shows how use background responses with ChatClientAgent and OpenAI Responses.

using Microsoft.Agents.AI;
using OpenAI;

var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-4o-mini";

AIAgent agent = new OpenAIClient(apiKey)
.GetOpenAIResponseClient(model)
.CreateAIAgent(instructions: "You are good at telling jokes.", name: "Joker");

// Enable background responses (only supported by OpenAI Responses at this time).
AgentRunOptions options = new() { AllowBackgroundResponses = true };

// Start the initial run.
AgentRunResponse response = await agent.RunAsync("Tell me a joke about a pirate.", options: options);

// Poll until the response is complete.
while (response.ContinuationToken is { } token)
{
// Wait before polling again.
await Task.Delay(TimeSpan.FromSeconds(2));

// Continue with the token.
options.ContinuationToken = token;

response = await agent.RunAsync([], options: options);
}

// Display the result.
Console.WriteLine(response.Text);

// Reset options for streaming.
options = new() { AllowBackgroundResponses = true };

AgentRunResponseUpdate? lastReceivedUpdate = null;
// Start streaming.
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("Tell me a joke about a pirate.", options: options))
{
// Output each update.
Console.Write(update.Text);

// Track last update.
lastReceivedUpdate = update;

// Simulate connection loss after first piece of content received.
if (update.Text.Length > 0)
{
break;
}
}

// Resume from interruption point.
options.ContinuationToken = lastReceivedUpdate?.ContinuationToken;

await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync([], options: options))
{
// Output each update.
Console.Write(update.Text);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# What This Sample Shows

This sample demonstrates how to use background responses with ChatCompletionAgent and OpenAI Responses for long-running operations. Background responses support:

- **Polling for completion** - Non-streaming APIs can start a background operation and return a continuation token. Poll with the token until the response completes.
- **Resuming after interruption** - Streaming APIs can be interrupted and resumed from the last update using the continuation token.

> **Note:** Background responses are currently only supported by OpenAI Responses.

# Prerequisites

Before you begin, ensure you have the following prerequisites:

- .NET 8.0 SDK or later
- OpenAI api key

Set the following environment variables:

```powershell
$env:OPENAI_APIKEY="*****" # Replace with your OpenAI api key
$env:OPENAI_MODEL="gpt-4o-mini" # Optional, defaults to gpt-4o-mini
1 change: 1 addition & 0 deletions dotnet/samples/GettingStarted/Agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Before you begin, ensure you have the following prerequisites:
|[Using middleware with an agent](./Agent_Step14_Middleware/)|This sample demonstrates how to use middleware with an agent|
|[Using plugins with an agent](./Agent_Step15_Plugins/)|This sample demonstrates how to use plugins with an agent|
|[Reducing chat history size](./Agent_Step16_ChatReduction/)|This sample demonstrates how to reduce the chat history to constrain its size, where chat history is maintained locally|
|[Background responses](./Agent_Step17_BackgroundResponses/)|This sample demonstrates how to use background responses for long-running operations with polling and resumption support|

## Running the samples from the console

Expand Down
45 changes: 44 additions & 1 deletion dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Agents.AI;
/// </summary>
/// <remarks>
/// <para>
/// This class currently has no options, but may be extended in the future to include additional configuration settings.
/// This class currently has minimal options, but may be extended in the future to include additional configuration settings.
/// </para>
/// <para>
/// Implementations of <see cref="AIAgent"/> may provide subclasses of <see cref="AgentRunOptions"/> with additional options specific to that agent type.
Expand All @@ -33,5 +33,48 @@ public AgentRunOptions()
public AgentRunOptions(AgentRunOptions options)
{
_ = Throw.IfNull(options);
this.ContinuationToken = options.ContinuationToken;
this.AllowBackgroundResponses = options.AllowBackgroundResponses;
}

/// <summary>
/// Gets or sets the continuation token for resuming and getting the result of the agent response identified by this token.
/// </summary>
/// <remarks>
/// This property is used for background responses that can be activated via the <see cref="AllowBackgroundResponses"/>
/// property if the <see cref="AIAgent"/> implementation supports them.
/// Streamed background responses, such as those returned by default by "AIAgent.RunStreamingAsync",
/// can be resumed if interrupted. This means that a continuation token obtained from the <see cref="AgentRunResponse.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 "AIAgent.RunAsync",
/// can be polled for completion by obtaining the token from the <see cref="AgentRunResponse.ContinuationToken"/> property
/// and passing it to this property on subsequent calls to "AIAgent.RunAsync".
/// </remarks>
public object? ContinuationToken { get; set; }

/// <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 may 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 may 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 that has one 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 implementation it's used with supports background responses.
/// If the implementation does not support background responses, this property will be ignored.
/// </para>
/// </remarks>
public bool? AllowBackgroundResponses { get; set; }
}
18 changes: 18 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public AgentRunResponse(ChatResponse response)
this.RawRepresentation = response;
this.ResponseId = response.ResponseId;
this.Usage = response.Usage;
this.ContinuationToken = response.ContinuationToken; // propagate continuation token
}

/// <summary>
Expand Down Expand Up @@ -159,6 +160,23 @@ public IList<ChatMessage> Messages
/// </value>
public string? ResponseId { get; set; }

/// <summary>
/// Gets or sets the continuation token for getting result of the background agent response.
/// </summary>
/// <remarks>
/// <see cref="AIAgent"/> implementations that support background responses will return
/// a continuation token if background responses are allowed in <see cref="AgentRunOptions.AllowBackgroundResponses"/>
/// and the result of the response has not been obtained yet. If the response has completed and the result has been obtained,
/// the token will be <see langword="null"/>.
/// <para>
/// This property should be used in conjunction with <see cref="AgentRunOptions.ContinuationToken"/> to
/// continue to poll for the completion of the response. Pass this token to
/// <see cref="AgentRunOptions.ContinuationToken"/> on subsequent calls to "AIAgent.RunAsync"
/// to poll for completion.
/// </para>
/// </remarks>
public object? ContinuationToken { get; set; }

/// <summary>
/// Gets or sets the timestamp indicating when this response was created.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ response.RawRepresentation as ChatResponse ??
RawRepresentation = response,
ResponseId = response.ResponseId,
Usage = response.Usage,
ContinuationToken = response.ContinuationToken,
};
}

Expand Down Expand Up @@ -74,6 +75,7 @@ responseUpdate.RawRepresentation as ChatResponseUpdate ??
RawRepresentation = responseUpdate,
ResponseId = responseUpdate.ResponseId,
Role = responseUpdate.Role,
ContinuationToken = responseUpdate.ContinuationToken,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public AgentRunResponseUpdate(ChatResponseUpdate chatResponseUpdate)
this.RawRepresentation = chatResponseUpdate;
this.ResponseId = chatResponseUpdate.ResponseId;
this.Role = chatResponseUpdate.Role;
this.ContinuationToken = chatResponseUpdate.ContinuationToken;
}

/// <summary>Gets or sets the name of the author of the response update.</summary>
Expand Down Expand Up @@ -148,6 +149,21 @@ public IList<AIContent> Contents
/// <summary>Gets or sets a timestamp for the response update.</summary>
public DateTimeOffset? CreatedAt { get; set; }

/// <summary>
/// Gets or sets the continuation token for resuming the streamed agent response of which this update is a part.
/// </summary>
/// <remarks>
/// <see cref="AIAgent"/> implementations that support background responses will return
/// a continuation token on each update if background responses are allowed in <see cref="AgentRunOptions.AllowBackgroundResponses"/>
/// except of the last update, for which 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="AgentRunOptions.ContinuationToken"/> on subsequent calls to "AIAgent.RunAsync"
/// to resume streaming from the point of interruption.
/// </para>
/// </remarks>
public object? ContinuationToken { get; set; }

/// <inheritdoc/>
public override string ToString() => this.Text;

Expand Down
18 changes: 15 additions & 3 deletions dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,13 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages) { InvokeException
// If no agent chat options were provided, return the request chat options as is.
if (this._agentOptions?.ChatOptions is null)
{
return requestChatOptions;
return ApplyBackgroundResponsesProperties(requestChatOptions, runOptions);
}

// If no request chat options were provided, use the agent's chat options clone.
if (requestChatOptions is null)
{
return this._agentOptions?.ChatOptions.Clone();
return ApplyBackgroundResponsesProperties(this._agentOptions?.ChatOptions.Clone(), runOptions);
}

// If both are present, we need to merge them.
Expand Down Expand Up @@ -532,7 +532,19 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages) { InvokeException
}
}

return requestChatOptions;
return ApplyBackgroundResponsesProperties(requestChatOptions, runOptions);

static ChatOptions? ApplyBackgroundResponsesProperties(ChatOptions? chatOptions, AgentRunOptions? agentRunOptions)
{
if (agentRunOptions?.AllowBackgroundResponses is not null)
{
chatOptions ??= new ChatOptions();
chatOptions.AllowBackgroundResponses = agentRunOptions.AllowBackgroundResponses;
chatOptions.ContinuationToken = agentRunOptions.ContinuationToken;
}

return chatOptions;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Text.Json;
using Microsoft.Extensions.AI;

namespace Microsoft.Agents.AI.Abstractions.UnitTests;

Expand All @@ -13,15 +15,44 @@ public class AgentRunOptionsTests
public void CloningConstructorCopiesProperties()
{
// Arrange
var options = new AgentRunOptions();
var options = new AgentRunOptions
{
ContinuationToken = new object(),
AllowBackgroundResponses = true
};

// Act
var clone = new AgentRunOptions(options);

// Assert
Assert.NotNull(clone);
Assert.Same(options.ContinuationToken, clone.ContinuationToken);
Assert.Equal(options.AllowBackgroundResponses, clone.AllowBackgroundResponses);
}

[Fact]
public void CloningConstructorThrowsIfNull() =>
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AgentRunOptions(null!));

[Fact]
public void JsonSerializationRoundtrips()
{
// Arrange
var options = new AgentRunOptions
{
ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
AllowBackgroundResponses = true
};

// Act
string json = JsonSerializer.Serialize(options, AgentAbstractionsJsonUtilities.DefaultOptions);

var deserialized = JsonSerializer.Deserialize<AgentRunOptions>(json, AgentAbstractionsJsonUtilities.DefaultOptions);

// Assert
Assert.NotNull(deserialized);
Assert.Equivalent(ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), deserialized!.ContinuationToken);
Assert.Equal(options.AllowBackgroundResponses, deserialized.AllowBackgroundResponses);
}
}
Loading
Loading