Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 5 additions & 5 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.8" />
<!-- AG-UI .NET SDK packages (published by the AG-UI team). -->
<PackageVersion Include="AGUI.Abstractions" Version="0.0.3" />
<PackageVersion Include="AGUI.Formatting" Version="0.0.3" />
<PackageVersion Include="AGUI.Protobuf" Version="0.0.3" />
<PackageVersion Include="AGUI.Client" Version="0.0.3" />
<PackageVersion Include="AGUI.Server" Version="0.0.3" />
<PackageVersion Include="AGUI.Abstractions" Version="0.0.4" />
<PackageVersion Include="AGUI.Formatting" Version="0.0.4" />
<PackageVersion Include="AGUI.Protobuf" Version="0.0.4" />
<PackageVersion Include="AGUI.Client" Version="0.0.4" />
<PackageVersion Include="AGUI.Server" Version="0.0.4" />
<PackageVersion Include="System.Text.Json" Version="10.0.9" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.9" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
Expand Down
32 changes: 14 additions & 18 deletions dotnet/samples/02-agents/AGUI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ dotnet run --urls http://localhost:8888
An interactive console client that connects to an AG-UI server. Demonstrates:

- Creating an AG-UI client with `AGUIChatClient`
- Managing conversation threads
- Managing multi-turn conversations with an `AgentSession`
- Streaming responses with `RunStreamingAsync`
- Displaying colored console output for different content types
- Supporting both interactive and automated modes
Expand Down Expand Up @@ -133,28 +133,24 @@ Demonstrates human-in-the-loop approval workflows for sensitive operations. This

An AG-UI server that implements approval workflows. Demonstrates:

- Wrapping tools with `ApprovalRequiredAIFunction`
- Converting `FunctionApprovalRequestContent` to approval requests
- Middleware pattern with `ServerFunctionApprovalServerAgent`
- Complete function call capture and restoration
- Wrapping a tool with `ApprovalRequiredAIFunction` so it requires approval before running
- Mapping a plain agent with `MapAGUIServer`, which natively emits an approval interrupt when the model calls the approval-required tool and resumes the run once the client sends the decision back

**Run the server:**

```bash
cd Step04_HumanInLoop/Server
dotnet run --urls http://localhost:8888
dotnet run --urls http://localhost:5100
```

#### Client (`Step04_HumanInLoop/Client`)

An interactive client that handles approval requests from the server. Demonstrates:

- Using `ServerFunctionApprovalClientAgent` middleware
- Detecting `FunctionApprovalRequestContent`
- Displaying approval details to users
- Prompting for approval/rejection
- Sending approval responses with `FunctionApprovalResponseContent`
- Resuming conversation after approval
- Detecting `ToolApprovalRequestContent` in the streamed response
- Displaying approval details to the user and prompting for approval or rejection
- Sending the decision back as a `ToolApprovalResponseContent` created with `approvalRequest.CreateResponse(approved)`
- Resuming the run so the server continues after the decision is received

**Run the client:**

Expand All @@ -167,15 +163,15 @@ Try asking the agent to perform sensitive operations like "Approve expense repor

### Step05_StateManagement

An AG-UI server and client that demonstrate state management with predictive updates.
An AG-UI server and client that demonstrate shared state management.

#### Server (`Step05_StateManagement/Server`)

Demonstrates:

- Defining state schemas using C# records
- Using `SharedStateAgent` middleware for state management
- Streaming predictive state updates with `AgentState` content
- Exposing a `generate_recipe` tool that returns the complete recipe
- Mapping the tool result to a `STATE_SNAPSHOT` event with `AGUIStreamOptions.MapResultAsStateSnapshot`
- Reading the client's current recipe from `RunAgentInput.State`
- Managing shared state between client and server
- Using JSON serialization contexts for state types

Expand Down Expand Up @@ -210,7 +206,7 @@ dotnet run

### Client-Side

1. `AGUIAgent` sends HTTP POST request to server
1. `AGUIChatClient` sends HTTP POST request to server
2. Server responds with SSE stream
3. Client parses events into `AgentResponseUpdate` objects
4. Updates are displayed based on content type
Expand All @@ -228,7 +224,7 @@ dotnet run

`ConversationId` keeps request/response continuity. It is not proof that the caller owns that conversation. In multi-user deployments, authenticate each AG-UI request and authorize conversation access using your application's real boundary, such as the authenticated user, tenant, or workspace.

If your ASP.NET Core host shares session storage across users, pair `MapAGUI` with an isolation strategy such as `UseClaimsBasedSessionIsolation(...)` so the storage key includes a principal-specific dimension instead of relying on the conversation identifier alone.
If your ASP.NET Core host shares session storage across users, pair `MapAGUIServer` with an isolation strategy such as `UseClaimsBasedSessionIsolation(...)` so the storage key includes a principal-specific dimension instead of relying on the conversation identifier alone.

## Troubleshooting

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

using AGUI.Abstractions;
using AGUI.Client;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
Expand Down Expand Up @@ -50,7 +49,6 @@

// Stream the response
bool isFirstUpdate = true;
string? threadId = null;

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
Expand All @@ -59,11 +57,8 @@
// First update indicates run started
if (isFirstUpdate)
{
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
// id is carried on the AG-UI RUN_STARTED event's raw representation.
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
Console.WriteLine($"\n[Run Started - Run: {chatUpdate.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
Expand All @@ -87,7 +82,7 @@
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
Console.WriteLine("\n[Run Finished]");
Console.ResetColor();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using OpenAI.Chat;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUIServer();

// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using AGUI.Abstractions;
using AGUI.Client;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
Expand Down Expand Up @@ -50,7 +49,6 @@

// Stream the response
bool isFirstUpdate = true;
string? threadId = null;

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
Expand All @@ -59,11 +57,8 @@
// First update indicates run started
if (isFirstUpdate)
{
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
// id is carried on the AG-UI RUN_STARTED event's raw representation.
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
Console.WriteLine($"\n[Run Started - Run: {chatUpdate.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
Expand Down Expand Up @@ -119,7 +114,7 @@
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
Console.WriteLine("\n[Run Finished]");
Console.ResetColor();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using OpenAI.Chat;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
builder.Services.AddAGUIServer();
Expand Down Expand Up @@ -75,6 +74,7 @@ static RestaurantSearchResponse SearchRestaurants(
[
AIFunctionFactory.Create(
SearchRestaurants,
name: "search_restaurants",
serializerOptions: jsonOptions.SerializerOptions)
];

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

using System.ComponentModel;
using AGUI.Abstractions;
using AGUI.Client;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
Expand Down Expand Up @@ -63,7 +62,6 @@ static string GetUserLocation()

// Stream the response
bool isFirstUpdate = true;
string? threadId = null;

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
Expand All @@ -72,11 +70,8 @@ static string GetUserLocation()
// First update indicates run started
if (isFirstUpdate)
{
// AGUIChatClient is stateless and never surfaces a ConversationId; the thread
// id is carried on the AG-UI RUN_STARTED event's raw representation.
threadId = (chatUpdate.RawRepresentation as RunStartedEvent)?.ThreadId;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {chatUpdate.ResponseId}]");
Console.WriteLine($"\n[Run Started - Run: {chatUpdate.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
Expand Down Expand Up @@ -112,7 +107,7 @@ static string GetUserLocation()
}

Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"\n[Run Finished - Thread: {threadId}]");
Console.WriteLine("\n[Run Finished]");
Console.ResetColor();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using OpenAI.Chat;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUIServer();

// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
Expand Down
26 changes: 4 additions & 22 deletions dotnet/samples/02-agents/AGUI/Step04_HumanInLoop/Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,12 @@

AGUIChatClient chatClient = new(new(httpClient, serverUrl));

// Create agent
ChatClientAgent baseAgent = chatClient.AsAIAgent(
// Create agent. No custom approval agent is required: the loop below handles the approval interrupt
// directly, and AGUIChatClient transports the decision back to the server via the AG-UI resume mechanism.
AIAgent agent = chatClient.AsAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant.");

// Use default JSON serializer options
JsonSerializerOptions jsonSerializerOptions = JsonSerializerOptions.Default;

// Wrap the agent with ServerFunctionApprovalClientAgent
ServerFunctionApprovalClientAgent agent = new(baseAgent, jsonSerializerOptions);

List<ChatMessage> messages = [];
AgentSession? session = null;

Expand All @@ -44,7 +39,6 @@
messages.Add(new ChatMessage(ChatRole.User, input));
Console.WriteLine();

#pragma warning disable MEAI001
List<AIContent> approvalResponses = [];

do
Expand All @@ -68,15 +62,6 @@

ToolApprovalResponseContent approvalResponse = approvalRequest.CreateResponse(approved);

if (approvalRequest.AdditionalProperties != null)
{
approvalResponse.AdditionalProperties = [];
foreach (var kvp in approvalRequest.AdditionalProperties)
{
approvalResponse.AdditionalProperties[kvp.Key] = kvp.Value;
}
}

approvalResponses.Add(approvalResponse);
break;

Expand Down Expand Up @@ -115,19 +100,17 @@
messages.AddRange(response.Messages);
foreach (AIContent approvalResponse in approvalResponses)
{
messages.Add(new ChatMessage(ChatRole.Tool, [approvalResponse]));
messages.Add(new ChatMessage(ChatRole.User, [approvalResponse]));
}
}
while (approvalResponses.Count > 0);
#pragma warning restore MEAI001

Console.WriteLine("\n");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Ask another question (or type 'exit' to quit):");
Console.ResetColor();
}

#pragma warning disable MEAI001
static void DisplayApprovalRequest(ToolApprovalRequestContent approvalRequest, FunctionCallContent fcc)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Expand All @@ -149,4 +132,3 @@ static void DisplayApprovalRequest(ToolApprovalRequestContent approvalRequest, F
Console.WriteLine("============================================================");
Console.ResetColor();
}
#pragma warning restore MEAI001
Loading
Loading