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
36 changes: 36 additions & 0 deletions dotnet/samples/GettingStarted/AGUI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,41 @@ cd Step05_StateManagement/Client
dotnet run
```

### Step06_DynamicAgentResolution

Demonstrates dynamic agent resolution based on route parameters. This enables multi-tenant applications, agent selection by ID, and runtime agent configuration.

#### Server (`Step06_DynamicAgentResolution/Server`)

An AG-UI server that dynamically selects agents based on route parameters. Demonstrates:

- Using `MapAGUI` with a factory delegate for dynamic agent resolution
- Extracting route parameters with `HttpContext.GetRouteValue`
- Returning `null` from the factory for 404 responses
- Supporting multiple agent configurations on a single route pattern

**Run the server:**

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

Available agents: `general`, `code`, `writer`

#### Client (`Step06_DynamicAgentResolution/Client`)

A client that demonstrates connecting to different dynamically-resolved agents.

**Run the client:**

```bash
cd Step06_DynamicAgentResolution/Client
dotnet run
```

The client allows you to switch between different agent types at runtime. Type `:back` to switch agents or `:q` to quit.

## How AG-UI Works

### Server-Side
Expand Down Expand Up @@ -284,6 +319,7 @@ The samples above demonstrate the AG-UI features currently available in C#:
- ✅ **Streaming Responses**: Real-time Server-Sent Events
- ✅ **State Management**: State schemas with predictive updates
- ✅ **Human-in-the-Loop**: Approval workflows for sensitive operations
- ✅ **Dynamic Agent Resolution**: Route-based agent selection for multi-tenant scenarios

### Coming Soon to C#

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>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

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

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

using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;

string serverUrl = Environment.GetEnvironmentVariable("AGUI_SERVER_URL") ?? "http://localhost:8888";

Console.WriteLine("AG-UI Dynamic Agent Resolution Demo");
Console.WriteLine("====================================\n");
Console.WriteLine("Available agents: general, code, writer");
Console.WriteLine($"Server URL: {serverUrl}\n");

using HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(60)
};

while (true)
{
// Get agent selection
Console.Write("Select agent (general/code/writer) or 'quit': ");
string? agentId = Console.ReadLine()?.Trim();

if (string.IsNullOrWhiteSpace(agentId) || agentId.Equals("quit", StringComparison.OrdinalIgnoreCase) || agentId == ":q")
{
break;
}

// Create client for specific agent
string agentUrl = $"{serverUrl.TrimEnd('/')}/agents/{agentId}";
AGUIChatClient chatClient = new(httpClient, agentUrl);

AIAgent agent = chatClient.CreateAIAgent(
name: $"agui-client-{agentId}",
description: $"AG-UI Client for {agentId} agent");

AgentThread thread = agent.GetNewThread();
List<ChatMessage> messages = [];

Console.WriteLine($"\nConnected to '{agentId}' agent. Type ':back' to switch agents.\n");

// Conversation loop for this agent
while (true)
{
Console.Write($"[{agentId}] User: ");
string? message = Console.ReadLine();

if (string.IsNullOrWhiteSpace(message))
{
continue;
}

if (message == ":back")
{
Console.WriteLine();
break;
}

if (message == ":q" || message == "quit")
{
return;
}

messages.Add(new ChatMessage(ChatRole.User, message));

// Stream the response
Console.Write($"[{agentId}] Assistant: ");

try
{
await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(messages, thread))
{
foreach (AIContent content in update.Contents)
{
if (content is TextContent textContent)
{
Console.Write(textContent.Text);
}
}
}

Console.WriteLine("\n");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"\nAgent '{agentId}' not found. Please try a different agent.\n");
Console.ResetColor();
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
Comment thread
TheEagleByte marked this conversation as resolved.

using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI.Chat;

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

WebApplication app = builder.Build();

string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"]
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
Comment thread
TheEagleByte marked this conversation as resolved.

// Create chat client (shared across agents)
ChatClient chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName);

// Define available agent configurations
Dictionary<string, (string Name, string Instructions)> agentConfigs = new(StringComparer.OrdinalIgnoreCase)
{
["general"] = ("GeneralAssistant", "You are a helpful general assistant."),
["code"] = ("CodeAssistant", "You are an expert programmer. Help users write, debug, and optimize code."),
["writer"] = ("WriterAssistant", "You are a professional writer. Help users with writing, editing, and creative content.")
};
Comment thread
TheEagleByte marked this conversation as resolved.

// Map AG-UI endpoint with dynamic agent resolution
app.MapAGUI("/agents/{agentId}", async (HttpContext context, CancellationToken cancellationToken) =>
{
// Extract agent ID from route
string? agentId = context.GetRouteValue("agentId")?.ToString();

if (string.IsNullOrEmpty(agentId))
{
return null;
}

// Look up agent configuration
if (!agentConfigs.TryGetValue(agentId, out (string Name, string Instructions) config))
{
return null; // Returns 404
}

// Create and return agent (could also cache this)
IChatClient client = chatClient.AsIChatClient();
return client.CreateAIAgent(
name: config.Name,
instructions: config.Instructions);
});

// Also map a root endpoint for discovery
app.MapGet("/", () => Results.Json(new
{
availableAgents = agentConfigs.Keys.ToArray(),
usage = "POST to /agents/{agentId} with AG-UI protocol"
}));

await app.RunAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:8888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7047;http://localhost:8888",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<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,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Loading