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
24 changes: 19 additions & 5 deletions dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// Configure the chat model and our agent.
builder.AddKeyedChatClient("chat-model");

builder.AddAIAgent(
var pirateAgent = builder.AddAIAgent(
"pirate",
instructions: "You are a pirate. Speak like a pirate",
description: "An agent that speaks like a pirate.",
Expand Down Expand Up @@ -78,8 +78,20 @@ Once the user has deduced what type (knight or knave) both Alice and Bob are, te
description: "An agent that helps with literature.",
chatClientServiceKey: "chat-model");

builder.AddSequentialWorkflow("science-sequential-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
builder.AddConcurrentWorkflow("science-concurrent-workflow", [chemistryAgent, mathsAgent, literatureAgent]).AddAsAIAgent();
var scienceSequentialWorkflow = builder.AddWorkflow("science-sequential-workflow", (sp, key) =>
{
List<IHostedAgentBuilder> usedAgents = [chemistryAgent, mathsAgent, literatureAgent];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, it's still counterintuitive to me that something called "Agent" and that's returned from a method called "AddAIAgent" is actually a builder. I understand why it is, but maybe there are things we could do to ease this.

var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildSequential(workflowName: key, agents: agents);
}).AddAsAIAgent();

var scienceConcurrentWorkflow = builder.AddWorkflow("science-concurrent-workflow", (sp, key) =>
{
List<IHostedAgentBuilder> usedAgents = [chemistryAgent, mathsAgent, literatureAgent];
var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildConcurrent(workflowName: key, agents: agents);
}).AddAsAIAgent();

builder.AddOpenAIResponses();

var app = builder.Build();
Expand All @@ -91,7 +103,7 @@ Once the user has deduced what type (knight or knave) both Alice and Bob are, te
app.UseExceptionHandler();

// attach a2a with simple message communication
app.MapA2A(agentName: "pirate", path: "/a2a/pirate");
app.MapA2A(pirateAgent, path: "/a2a/pirate");
app.MapA2A(agentName: "knights-and-knaves", path: "/a2a/knights-and-knaves", agentCard: new()
{
Name = "Knights and Knaves",
Expand All @@ -104,8 +116,10 @@ Once the user has deduced what type (knight or knave) both Alice and Bob are, te

app.MapOpenAIResponses();

app.MapOpenAIChatCompletions("pirate");
app.MapOpenAIChatCompletions(pirateAgent);
app.MapOpenAIChatCompletions("knights-and-knaves");
app.MapOpenAIChatCompletions(scienceSequentialWorkflow);
app.MapOpenAIChatCompletions(scienceConcurrentWorkflow);

// Map the agents HTTP endpoints
app.MapAgentDiscovery("/agents");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ namespace Microsoft.AspNetCore.Builder;
/// </summary>
public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
{
/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
/// <param name="path">The route group to use for A2A endpoints.</param>
/// <returns>Configured <see cref="ITaskManager"/> for A2A integration.</returns>
/// <remarks>
/// This method can be used to access A2A agents that support the
/// <see href="https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md#2-curated-registries-catalog-based-discovery">Curated Registries (Catalog-Based Discovery)</see>
/// discovery mechanism.
/// </remarks>
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path)
=> endpoints.MapA2A(agentBuilder, path, _ => { });

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
Expand All @@ -28,6 +43,25 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path)
=> endpoints.MapA2A(agentName, path, _ => { });

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
/// <param name="path">The route group to use for A2A endpoints.</param>
/// <param name="configureTaskManager">The callback to configure <see cref="ITaskManager"/>.</param>
/// <returns>Configured <see cref="ITaskManager"/> for A2A integration.</returns>
/// <remarks>
/// This method can be used to access A2A agents that support the
/// <see href="https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md#2-curated-registries-catalog-based-discovery">Curated Registries (Catalog-Based Discovery)</see>
/// discovery mechanism.
/// </remarks>
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, Action<ITaskManager> configureTaskManager)
{
ArgumentNullException.ThrowIfNull(agentBuilder);
return endpoints.MapA2A(agentBuilder.Name, path, configureTaskManager);
}

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
Expand All @@ -42,6 +76,22 @@ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpo
return endpoints.MapA2A(agent, path, configureTaskManager);
}

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
/// <param name="path">The route group to use for A2A endpoints.</param>
/// <param name="agentCard">Agent card info to return on query.</param>
/// <returns>Configured <see cref="ITaskManager"/> for A2A integration.</returns>
/// <remarks>
/// This method can be used to access A2A agents that support the
/// <see href="https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md#2-curated-registries-catalog-based-discovery">Curated Registries (Catalog-Based Discovery)</see>
/// discovery mechanism.
/// </remarks>
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, AgentCard agentCard)
=> endpoints.MapA2A(agentBuilder, path, agentCard, _ => { });

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
Expand All @@ -58,6 +108,26 @@ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpo
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, AgentCard agentCard)
=> endpoints.MapA2A(agentName, path, agentCard, _ => { });

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
/// <param name="path">The route group to use for A2A endpoints.</param>
/// <param name="agentCard">Agent card info to return on query.</param>
/// <param name="configureTaskManager">The callback to configure <see cref="ITaskManager"/>.</param>
/// <returns>Configured <see cref="ITaskManager"/> for A2A integration.</returns>
/// <remarks>
/// This method can be used to access A2A agents that support the
/// <see href="https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md#2-curated-registries-catalog-based-discovery">Curated Registries (Catalog-Based Discovery)</see>
/// discovery mechanism.
/// </remarks>
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, AgentCard agentCard, Action<ITaskManager> configureTaskManager)
{
ArgumentNullException.ThrowIfNull(agentBuilder);
return endpoints.MapA2A(agentBuilder.Name, path, agentCard, configureTaskManager);
}

/// <summary>
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -16,13 +17,28 @@ namespace Microsoft.AspNetCore.Builder;

public static partial class MicrosoftAgentAIHostingOpenAIEndpointRouteBuilderExtensions
{
/// <summary>
/// Maps OpenAI ChatCompletions API endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the OpenAI ChatCompletions endpoints to.</param>
/// <param name="agentBuilder">The builder for specific <see cref="AIAgent"/> to map OpenAI ChatCompletions to.</param>
/// <param name="path">Custom route path for the chat completions endpoint.</param>
public static IEndpointConventionBuilder MapOpenAIChatCompletions(
this IEndpointRouteBuilder endpoints,
IHostedAgentBuilder agentBuilder,
[StringSyntax("Route")] string? path = null)
{
ArgumentNullException.ThrowIfNull(agentBuilder);
return endpoints.MapOpenAIChatCompletions(agentBuilder.Name, path);
}

/// <summary>
/// Maps OpenAI ChatCompletions API endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the OpenAI ChatCompletions endpoints to.</param>
/// <param name="agentName">The name of the AI agent service registered in the dependency injection container. This name is used to resolve the <see cref="AIAgent"/> instance from the keyed services.</param>
/// <param name="path">Custom route path for the chat completions endpoint.</param>
public static void MapOpenAIChatCompletions(
public static IEndpointConventionBuilder MapOpenAIChatCompletions(
this IEndpointRouteBuilder endpoints,
string agentName,
[StringSyntax("Route")] string? path = null)
Expand All @@ -39,6 +55,8 @@ public static void MapOpenAIChatCompletions(
path ??= $"/{agentName}/v1/chat/completions";
var chatCompletionsRouteGroup = endpoints.MapGroup(path);
MapChatCompletions(chatCompletionsRouteGroup, agent);

return chatCompletionsRouteGroup;
}

private static void MapChatCompletions(IEndpointRouteBuilder routeGroup, AIAgent agent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.OpenAI.Responses;
using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models;
using Microsoft.AspNetCore.Http;
Expand All @@ -18,6 +19,30 @@ namespace Microsoft.AspNetCore.Builder;
/// </summary>
public static partial class MicrosoftAgentAIHostingOpenAIEndpointRouteBuilderExtensions
{
/// <summary>
/// Maps OpenAI Responses API endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the OpenAI Responses endpoints to.</param>
/// <param name="agentBuilder">The builder for <see cref="AIAgent"/> to map the OpenAI Responses endpoints for.</param>
/// <param name="responsesPath">Custom route path for the responses endpoint.</param>
public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, [StringSyntax("Route")] string? responsesPath = null)
{
ArgumentNullException.ThrowIfNull(agentBuilder);
return MapOpenAIResponses(endpoints, agentBuilder.Name, responsesPath);
}

/// <summary>
/// Maps OpenAI Responses API endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the OpenAI Responses endpoints to.</param>
/// <param name="agentName">The name of <see cref="AIAgent"/> to map the OpenAI Responses endpoints for.</param>
/// <param name="responsesPath">Custom route path for the responses endpoint.</param>
public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, string agentName, [StringSyntax("Route")] string? responsesPath = null)
{
var agent = endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(agentName);
return endpoints.MapOpenAIResponses(agent, responsesPath);
}

/// <summary>
/// Maps OpenAI Responses API endpoints to the specified <see cref="IEndpointRouteBuilder"/> for the given <see cref="AIAgent"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Hosting.Local;
using Microsoft.Agents.AI.Workflows;
Expand All @@ -16,46 +15,6 @@ namespace Microsoft.Agents.AI.Hosting;
/// </summary>
public static class HostApplicationBuilderWorkflowExtensions
{
/// <summary>
/// Registers a concurrent workflow that executes multiple agents in parallel.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="name">The unique name for the workflow.</param>
/// <param name="agentBuilders">A collection of <see cref="IHostedAgentBuilder"/> instances representing agents to execute concurrently.</param>
/// <returns>An <see cref="IHostedWorkflowBuilder"/> that can be used to further configure the workflow.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="name"/>, or <paramref name="agentBuilders"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> or <paramref name="agentBuilders"/> is empty.</exception>
public static IHostedWorkflowBuilder AddConcurrentWorkflow(this IHostApplicationBuilder builder, string name, IEnumerable<IHostedAgentBuilder> agentBuilders)
{
Throw.IfNullOrEmpty(agentBuilders);

return builder.AddWorkflow(name, (sp, key) =>
{
var agents = agentBuilders.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildConcurrent(workflowName: name, agents: agents);
});
}

/// <summary>
/// Registers a sequential workflow that executes agents in a specific order.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/> to configure.</param>
/// <param name="name">The unique name for the workflow.</param>
/// <param name="agentBuilders">A collection of <see cref="IHostedAgentBuilder"/> instances representing agents to execute in sequence.</param>
/// <returns>An <see cref="IHostedWorkflowBuilder"/> that can be used to further configure the workflow.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/>, <paramref name="name"/>, or <paramref name="agentBuilders"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> or <paramref name="agentBuilders"/> is empty.</exception>
public static IHostedWorkflowBuilder AddSequentialWorkflow(this IHostApplicationBuilder builder, string name, IEnumerable<IHostedAgentBuilder> agentBuilders)
{
Throw.IfNullOrEmpty(agentBuilders);

return builder.AddWorkflow(name, (sp, key) =>
{
var agents = agentBuilders.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
return AgentWorkflowBuilder.BuildSequential(workflowName: name, agents: agents);
});
}

/// <summary>
/// Registers a custom workflow using a factory delegate.
/// </summary>
Expand Down
Loading
Loading