mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Add MapAGUI hosting overloads with IHostedAgentBuilder and agent name support
This adds the same hosting patterns from A2A and OpenAI to AGUI: - MapAGUI(IHostedAgentBuilder) and MapAGUI(IHostedAgentBuilder, string? path) - MapAGUI(string agentName) and MapAGUI(string agentName, string? path) - MapAGUI(AIAgent) and MapAGUI(AIAgent, string? path) - ValidateAgentName for URL-safe validation - Updated namespace to Microsoft.AspNetCore.Builder - Renamed class to MicrosoftAgentAIHostingAGUIEndpointRouteBuilderExtensions - Added comprehensive unit tests
This commit is contained in:
+95
-17
@@ -1,11 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.Shared;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
@@ -14,40 +17,106 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
|
||||
namespace Microsoft.AspNetCore.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for mapping AG-UI agents to ASP.NET Core endpoints.
|
||||
/// </summary>
|
||||
public static class AGUIEndpointRouteBuilderExtensions
|
||||
public static class MicrosoftAgentAIHostingAGUIEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps an AG-UI agent endpoint.
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="IHostedAgentBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The endpoint route builder.</param>
|
||||
/// <param name="pattern">The URL pattern for the endpoint.</param>
|
||||
/// <param name="aiAgent">The agent instance.</param>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agentBuilder">The builder for <see cref="AIAgent"/> to map the AG-UI endpoints for.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder)
|
||||
=> MapAGUI(endpoints, agentBuilder, path: null);
|
||||
|
||||
/// <summary>
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="IHostedAgentBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agentBuilder">The builder for <see cref="AIAgent"/> to map the AG-UI endpoints for.</param>
|
||||
/// <param name="path">Custom route path for the AG-UI endpoint.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, [StringSyntax("Route")] string? path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentNullException.ThrowIfNull(agentBuilder);
|
||||
|
||||
AIAgent agent = endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(agentBuilder.Name);
|
||||
return MapAGUI(endpoints, agent, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given agent name.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to map the AG-UI endpoints for.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(this IEndpointRouteBuilder endpoints, string agentName)
|
||||
=> MapAGUI(endpoints, agentName, path: null);
|
||||
|
||||
/// <summary>
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given agent name.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to map the AG-UI endpoints for.</param>
|
||||
/// <param name="path">Custom route path for the AG-UI endpoint.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(this IEndpointRouteBuilder endpoints, string agentName, [StringSyntax("Route")] string? path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(agentName);
|
||||
|
||||
AIAgent agent = endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
return MapAGUI(endpoints, agent, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agent">The <see cref="AIAgent"/> instance to map the AG-UI endpoints for.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(this IEndpointRouteBuilder endpoints, AIAgent agent)
|
||||
=> MapAGUI(endpoints, agent, path: null);
|
||||
|
||||
/// <summary>
|
||||
/// Maps AG-UI endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the AG-UI endpoints to.</param>
|
||||
/// <param name="agent">The <see cref="AIAgent"/> instance to map the AG-UI endpoints for.</param>
|
||||
/// <param name="path">Custom route path for the AG-UI endpoint.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for the mapped endpoint.</returns>
|
||||
public static IEndpointConventionBuilder MapAGUI(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
[StringSyntax("route")] string pattern,
|
||||
AIAgent aiAgent)
|
||||
AIAgent agent,
|
||||
[StringSyntax("Route")] string? path)
|
||||
{
|
||||
return endpoints.MapPost(pattern, async ([FromBody] RunAgentInput? input, HttpContext context, CancellationToken cancellationToken) =>
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentNullException.ThrowIfNull(agent);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent.Name));
|
||||
ValidateAgentName(agent.Name);
|
||||
|
||||
path ??= $"/{agent.Name}/agui";
|
||||
|
||||
return endpoints.MapPost(path, async ([FromBody] RunAgentInput? input, HttpContext context, CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (input is null)
|
||||
{
|
||||
return Results.BadRequest();
|
||||
}
|
||||
|
||||
var jsonOptions = context.RequestServices.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
|
||||
var jsonSerializerOptions = jsonOptions.Value.SerializerOptions;
|
||||
IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions> jsonOptions = context.RequestServices.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
|
||||
System.Text.Json.JsonSerializerOptions jsonSerializerOptions = jsonOptions.Value.SerializerOptions;
|
||||
|
||||
var messages = input.Messages.AsChatMessages(jsonSerializerOptions);
|
||||
var clientTools = input.Tools?.AsAITools().ToList();
|
||||
IEnumerable<ChatMessage> messages = input.Messages.AsChatMessages(jsonSerializerOptions);
|
||||
List<AITool>? clientTools = input.Tools?.AsAITools().ToList();
|
||||
|
||||
// Create run options with AG-UI context in AdditionalProperties
|
||||
var runOptions = new ChatClientAgentRunOptions
|
||||
ChatClientAgentRunOptions runOptions = new()
|
||||
{
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
@@ -64,7 +133,7 @@ public static class AGUIEndpointRouteBuilderExtensions
|
||||
};
|
||||
|
||||
// Run the agent and convert to AG-UI events
|
||||
var events = aiAgent.RunStreamingAsync(
|
||||
IAsyncEnumerable<BaseEvent> events = agent.RunStreamingAsync(
|
||||
messages,
|
||||
options: runOptions,
|
||||
cancellationToken: cancellationToken)
|
||||
@@ -76,8 +145,17 @@ public static class AGUIEndpointRouteBuilderExtensions
|
||||
jsonSerializerOptions,
|
||||
cancellationToken);
|
||||
|
||||
var sseLogger = context.RequestServices.GetRequiredService<ILogger<AGUIServerSentEventsResult>>();
|
||||
ILogger<AGUIServerSentEventsResult> sseLogger = context.RequestServices.GetRequiredService<ILogger<AGUIServerSentEventsResult>>();
|
||||
return new AGUIServerSentEventsResult(events, sseLogger);
|
||||
});
|
||||
}
|
||||
|
||||
private static void ValidateAgentName([NotNull] string agentName)
|
||||
{
|
||||
string escaped = Uri.EscapeDataString(agentName);
|
||||
if (!string.Equals(escaped, agentName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ArgumentException($"Agent name '{agentName}' contains characters invalid for URL routes.", nameof(agentName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user