Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
fix: add ConfigureAwait(false) to asynchronous calls that aren't the …
…ASP.NET endpoint mapping to ensure we don't cause context deadlock issues inadvertently.

Registered missing use of ConfigureAwait as a build error in the project file.
  • Loading branch information
ckpearson committed May 29, 2025
commit c2002df01f1908c54a3449953e1b28c8a84a453d
1 change: 1 addition & 0 deletions dotnet-sdk/AGUIDotnet/AGUIDotnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WarningsAsErrors>CA2007</WarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions dotnet-sdk/AGUIDotnet/Agent/AgentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static async IAsyncEnumerable<BaseEvent> RunToCompletionAsync(
{
try
{
await agent.RunAsync(input, channel.Writer, cancellationToken);
await agent.RunAsync(input, channel.Writer, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Expand All @@ -56,7 +56,7 @@ public static async IAsyncEnumerable<BaseEvent> RunToCompletionAsync(
}, cancellationToken);

// Enumerate the events produced by the agent and yield them to the caller.
await foreach (var ev in channel.Reader.ReadAllAsync(cancellationToken))
await foreach (var ev in channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
yield return ev;
}
Expand Down
36 changes: 18 additions & 18 deletions dotnet-sdk/AGUIDotnet/Agent/ChatClientAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ [.. chatOpts.Tools.OfType<AIFunction>().Where(f => f is not FrontendTool)],
input,
events,
cancellationToken
)).Where(t => t is not FrontendTool).ToImmutableList();
).ConfigureAwait(false)).Where(t => t is not FrontendTool).ToImmutableList();

var frontendTools = await PrepareFrontendTools(
[.. input.Tools.Select(t => new FrontendTool(t))],
input,
cancellationToken);
cancellationToken).ConfigureAwait(false);

var frontendToolNames = frontendTools.Select(t => t.Name).ToImmutableHashSet();

Expand All @@ -136,17 +136,17 @@ [.. chatOpts.Tools.OfType<AIFunction>().Where(f => f is not FrontendTool)],
chatOpts.AllowMultipleToolCalls = false;
}

var context = await PrepareContext(input, cancellationToken);
var mappedMessages = await MapAGUIMessagesToChatClientMessages(input, context, cancellationToken);
var context = await PrepareContext(input, cancellationToken).ConfigureAwait(false);
var mappedMessages = await MapAGUIMessagesToChatClientMessages(input, context, cancellationToken).ConfigureAwait(false);

var inFlightFrontendCalls = new HashSet<string>();

// Handle the run starting
await OnRunStartedAsync(input, events, cancellationToken);
await OnRunStartedAsync(input, events, cancellationToken).ConfigureAwait(false);

string? currentResponseId = null;

await foreach (var update in _chatClient.GetStreamingResponseAsync(mappedMessages, chatOpts, cancellationToken))
await foreach (var update in _chatClient.GetStreamingResponseAsync(mappedMessages, chatOpts, cancellationToken).ConfigureAwait(false))
{
/*
The function invocation loop provided by the chat client abstractions will still yield function result contents
Expand Down Expand Up @@ -176,7 +176,7 @@ await events.WriteAsync(new TextMessageEndEvent
{
MessageId = currentResponseId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);

currentResponseId = null;
}
Expand All @@ -190,7 +190,7 @@ await events.WriteAsync(new TextMessageStartEvent
{
MessageId = currentResponseId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}

Debug.Assert(currentResponseId is not null, "Handling text content without a current response message ID.");
Expand All @@ -203,7 +203,7 @@ await events.WriteAsync(new TextMessageContentEvent
MessageId = currentResponseId,
Delta = text.Text,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}
}
break;
Expand All @@ -224,7 +224,7 @@ await events.WriteAsync(new TextMessageEndEvent
{
MessageId = currentResponseId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
currentResponseId = null;
}

Expand All @@ -240,7 +240,7 @@ await events.WriteAsync(new ToolCallStartEvent
ToolCallName = functionCall.Name,
ParentMessageId = update.MessageId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);

await events.WriteAsync(new ToolCallArgsEvent
{
Expand All @@ -249,13 +249,13 @@ await events.WriteAsync(new ToolCallArgsEvent
// todo: an alternative might be not exposing the underlying channel writer, and instead providing a structured type for emitting common events.
Delta = JsonSerializer.Serialize(functionCall.Arguments, _jsonSerOpts),
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);

await events.WriteAsync(new ToolCallEndEvent
{
ToolCallId = functionCall.CallId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}

break;
Expand All @@ -276,15 +276,15 @@ await events.WriteAsync(new TextMessageEndEvent
{
MessageId = currentResponseId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}

await events.WriteAsync(new RunFinishedEvent
{
ThreadId = input.ThreadId,
RunId = input.RunId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);

events.Complete();
}
Expand Down Expand Up @@ -360,7 +360,7 @@ You are an expert at extracting context from a provided system message.
"""
),
cancellationToken: cancellationToken
);
).ConfigureAwait(false);

if (extractedContext.Usage is not null)
{
Expand Down Expand Up @@ -428,7 +428,7 @@ [.. input.Messages.Where(m => m is not SystemMessage)
.Prepend(new SystemMessage
{
Id = Guid.NewGuid().ToString(),
Content = await PrepareSystemMessage(input, sysMessage, context)
Content = await PrepareSystemMessage(input, sysMessage, context).ConfigureAwait(false)
})],

// Fallback to just preserving inbound messages as-is.
Expand Down Expand Up @@ -497,6 +497,6 @@ await events.WriteAsync(new RunStartedEvent
ThreadId = input.ThreadId,
RunId = input.RunId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}
}
16 changes: 8 additions & 8 deletions dotnet-sdk/AGUIDotnet/Agent/EchoAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ await events.WriteAsync(
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
},
ct
);
).ConfigureAwait(false);

var lastMessage = input.Messages.LastOrDefault();

await Task.Delay(500, ct);
await Task.Delay(500, ct).ConfigureAwait(false);

switch (lastMessage)
{
Expand All @@ -37,7 +37,7 @@ await events.WriteAsync(
$"Echoing system message:\n\n```\n{system.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
await events.WriteAsync(ev, ct).ConfigureAwait(false);
}
break;

Expand All @@ -46,7 +46,7 @@ await events.WriteAsync(
$"Echoing user message:\n\n```\n{user.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
await events.WriteAsync(ev, ct).ConfigureAwait(false);
}
break;

Expand All @@ -55,7 +55,7 @@ await events.WriteAsync(
$"Echoing assistant message:\n\n```\n{assistant.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
await events.WriteAsync(ev, ct).ConfigureAwait(false);
}
break;

Expand All @@ -64,7 +64,7 @@ await events.WriteAsync(
$"Echoing tool message for tool call '{tool.ToolCallId}':\n\n```\n{tool.Content}\n```\n"
))
{
await events.WriteAsync(ev, ct);
await events.WriteAsync(ev, ct).ConfigureAwait(false);
}
break;

Expand All @@ -73,7 +73,7 @@ await events.WriteAsync(
$"Unknown message type: {lastMessage?.GetType().Name ?? "null"}"
))
{
await events.WriteAsync(ev, ct);
await events.WriteAsync(ev, ct).ConfigureAwait(false);
}
break;
}
Expand All @@ -86,7 +86,7 @@ await events.WriteAsync(
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
},
ct
);
).ConfigureAwait(false);

events.Complete();
}
Expand Down
10 changes: 5 additions & 5 deletions dotnet-sdk/AGUIDotnet/Agent/StatefulChatClientAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void UpdateState(TState newState)

protected override async ValueTask<string> PrepareSystemMessage(RunAgentInput input, string systemMessage, ImmutableList<Context> context)
{
var coreMessage = await base.PrepareSystemMessage(input, systemMessage, context);
var coreMessage = await base.PrepareSystemMessage(input, systemMessage, context).ConfigureAwait(false);

// Hijack the original system message to include some context to the LLM about the stateful nature of this agent.
// Nudging it to use the state collaboration tools available to it.
Expand Down Expand Up @@ -77,7 +77,7 @@ protected override async ValueTask<string> PrepareSystemMessage(RunAgentInput in
protected override async ValueTask<ImmutableList<AIFunction>> PrepareBackendTools(ImmutableList<AIFunction> backendTools, RunAgentInput input, ChannelWriter<BaseEvent> events, CancellationToken cancellationToken = default)
{
return [
.. await base.PrepareBackendTools(backendTools, input, events, cancellationToken),
.. await base.PrepareBackendTools(backendTools, input, events, cancellationToken).ConfigureAwait(false),
AIFunctionFactory.Create(
RetrieveState,
name: "retrieve_state",
Expand All @@ -91,7 +91,7 @@ .. await base.PrepareBackendTools(backendTools, input, events, cancellationToken
await events.WriteAsync(new StateDeltaEvent {
Delta = [.. delta.Operations.Cast<object>()],
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}
},
name: "update_state",
Expand All @@ -103,7 +103,7 @@ await events.WriteAsync(new StateDeltaEvent {
protected override async ValueTask OnRunStartedAsync(RunAgentInput input, ChannelWriter<BaseEvent> events, CancellationToken cancellationToken = default)
{
// Allow the base behaviour of emitting the RunStartedEvent
await base.OnRunStartedAsync(input, events, cancellationToken);
await base.OnRunStartedAsync(input, events, cancellationToken).ConfigureAwait(false);

// Take the initial state from the input if possible
try
Expand All @@ -126,6 +126,6 @@ await events.WriteAsync(new StateSnapshotEvent
{
Snapshot = _currentState,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}, cancellationToken);
}, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ Func<IServiceProvider, IAGUIAgent> agentFactory
) =>
{
context.Response.ContentType = "text/event-stream";
await context.Response.Body.FlushAsync();
await context.Response.Body.FlushAsync().ConfigureAwait(true);

var serOptions = jsonOptions.Value.SerializerOptions;
var agent = agentFactory(context.RequestServices);

await foreach (var ev in agent.RunToCompletionAsync(input, context.RequestAborted))
await foreach (var ev in agent.RunToCompletionAsync(input, context.RequestAborted).ConfigureAwait(true))
{
var serializedEvent = JsonSerializer.Serialize(ev, serOptions);
await context.Response.WriteAsync($"data: {serializedEvent}\n\n");
await context.Response.Body.FlushAsync();
await context.Response.WriteAsync($"data: {serializedEvent}\n\n").ConfigureAwait(true);
await context.Response.Body.FlushAsync().ConfigureAwait(true);

// If the event is a RunFinishedEvent, we can break the loop.
if (ev is RunFinishedEvent)
Expand Down