mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
66e02c10e3
* update a2a agent to the latest a2a sdk (#5257) * Move A2A samples from 04-hosting to 02-agents (#5267) Move the A2A sample projects (A2AAgent_AsFunctionTools and A2AAgent_PollingForTaskCompletion) from samples/04-hosting/A2A/ to samples/02-agents/A2A/ to better align with the sample directory structure. Update solution file and samples README accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Fix stream reconnection for A2AAgent (#5275) * Add SSE stream reconnection support to A2AAgent Implement automatic reconnection for SSE streams that disconnect mid-task, using the Last-Event-ID header to resume from where the stream left off. Changes: - Add InvokeStreamingWithReconnectAsync method to A2AAgent with configurable max retries and delay between attempts - Add new log messages for reconnection events - Add A2AAgent_StreamReconnection sample demonstrating the feature - Update existing polling sample to use simplified SendMessageAsync API - Add unit tests for stream reconnection logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address comments * Address PR review feedback - Dispose SSE enumerator before GetTaskAsync fallback to release HTTP connection - Wrap StreamWriter in using blocks with leaveOpen:true and explicit UTF-8 encoding - Print update.Text instead of update object in stream reconnection sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Use IA2AClientFactory to create A2AClient (#5277) * Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample - Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions - Update A2ACardResolverExtensions to accept IA2AClientFactory - Update A2AClientExtensions to accept IA2AClientFactory - Update A2AAgent to use IA2AClientFactory for client creation - Add A2AAgent_ProtocolSelection sample demonstrating protocol selection - Add comprehensive unit tests for all changes - Update README files with new sample reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reorder params: options before loggerFactory in A2A extensions Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent and GetAIAgentAsync extension methods to follow the repo convention of keeping LoggerFactory and CancellationToken as the last parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Migrate A2A hosting to A2A SDK v1 (#5363) * .NET: Migrate A2A hosting to A2A SDK v1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove unused agent card --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Split A2A endpoint mapping into protocol-specific methods (#5413) * .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions - Rename A2AHostingOptions to A2AServerRegistrationOptions - Move server registration logic from A2AEndpointRouteBuilderExtensions and AIAgentExtensions into new A2AServerServiceCollectionExtensions - Remove A2AProtocolBinding and AIAgentExtensions (consolidated) - Update samples and tests to use the new registration API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address copilot comments --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary using directive in AgentWebChat.AgentHost Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * restore AsyncEnumerable package version * address copilot initial feedback * address automated code review and formatting issues * fix formatting issues * Add DI wiring verification tests for AddA2AServer Add three tests to A2AServerServiceCollectionExtensionsTests that verify custom keyed services are actually wired through to the A2AServer, not just that the server resolves non-null: - Custom IAgentHandler: verifies the keyed handler is invoked when processing a SendMessageRequest instead of the default A2AAgentHandler. - Custom AgentSessionStore (no handler): verifies the keyed session store's GetSessionAsync is called during request processing when no custom handler is registered. - Default stores end-to-end: verifies the InMemoryAgentSessionStore and InMemoryTaskStore defaults successfully process a request. Uses a new CreateAgentMockForRequests helper that includes SerializeSessionCoreAsync setup needed by InMemoryAgentSessionStore. All tests call A2AServer.SendMessageAsync directly (no HTTP layer needed) and use CancellationToken timeouts to guard against hangs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
176 lines
6.9 KiB
C#
176 lines
6.9 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using AgentWebChat.AgentHost;
|
|
using AgentWebChat.AgentHost.Custom;
|
|
using AgentWebChat.AgentHost.Utilities;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Agents.AI.DevUI;
|
|
using Microsoft.Agents.AI.Hosting;
|
|
using Microsoft.Agents.AI.Workflows;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Add service defaults & Aspire client integrations.
|
|
builder.AddServiceDefaults();
|
|
builder.Services.AddOpenApi();
|
|
|
|
// Add services to the container.
|
|
builder.Services.AddProblemDetails();
|
|
|
|
// Configure the chat model and our agent.
|
|
builder.AddKeyedChatClient("chat-model");
|
|
|
|
// Add DevUI services
|
|
builder.AddDevUI();
|
|
|
|
// Add OpenAI services
|
|
builder.AddOpenAIChatCompletions();
|
|
builder.AddOpenAIResponses();
|
|
|
|
var pirateAgentBuilder = builder.AddAIAgent(
|
|
"pirate",
|
|
instructions: "You are a pirate. Speak like a pirate",
|
|
description: "An agent that speaks like a pirate.",
|
|
chatClientServiceKey: "chat-model")
|
|
.WithAITool(new CustomAITool())
|
|
.WithAITool(new CustomFunctionTool())
|
|
.WithInMemorySessionStore();
|
|
|
|
var knightsKnavesAgentBuilder = builder.AddAIAgent("knights-and-knaves", (sp, key) =>
|
|
{
|
|
var chatClient = sp.GetRequiredKeyedService<IChatClient>("chat-model");
|
|
|
|
ChatClientAgent knight = new(
|
|
chatClient,
|
|
"""
|
|
You are a knight. This means that you must always tell the truth. Your name is Alice.
|
|
Bob is standing next to you. Bob is a knave, which means he always lies.
|
|
When replying, always start with your name (Alice). Eg, "Alice: I am a knight."
|
|
""", "Alice");
|
|
|
|
ChatClientAgent knave = new(
|
|
chatClient,
|
|
"""
|
|
You are a knave. This means that you must always lie. Your name is Bob.
|
|
Alice is standing next to you. Alice is a knight, which means she always tells the truth.
|
|
When replying, always include your name (Bob). Eg, "Bob: I am a knight."
|
|
""", "Bob");
|
|
|
|
ChatClientAgent narrator = new(
|
|
chatClient,
|
|
"""
|
|
You are are the narrator of a puzzle involving knights (who always tell the truth) and knaves (who always lie).
|
|
The user is going to ask questions and guess whether Alice or Bob is the knight or knave.
|
|
Alice is standing to one side of you. Alice is a knight, which means she always tells the truth.
|
|
Bob is standing to the other side of you. Bob is a knave, which means he always lies.
|
|
When replying, always include your name (Narrator).
|
|
Once the user has deduced what type (knight or knave) both Alice and Bob are, tell them whether they are right or wrong.
|
|
If the user asks a general question about their surrounding, make something up which is consistent with the scenario.
|
|
""", "Narrator");
|
|
|
|
return AgentWorkflowBuilder.BuildConcurrent([knight, knave, narrator]).AsAIAgent(name: key);
|
|
});
|
|
|
|
// Workflow consisting of multiple specialized agents
|
|
var chemistryAgent = builder.AddAIAgent("chemist",
|
|
instructions: "You are a chemistry expert. Answer thinking from the chemistry perspective",
|
|
description: "An agent that helps with chemistry.",
|
|
chatClientServiceKey: "chat-model");
|
|
|
|
var mathsAgent = builder.AddAIAgent("mathematician",
|
|
instructions: "You are a mathematics expert. Answer thinking from the maths perspective",
|
|
description: "An agent that helps with mathematics.",
|
|
chatClientServiceKey: "chat-model");
|
|
|
|
var literatureAgent = builder.AddAIAgent("literator",
|
|
instructions: "You are a literature expert. Answer thinking from the literature perspective",
|
|
description: "An agent that helps with literature.",
|
|
chatClientServiceKey: "chat-model");
|
|
|
|
var scienceSequentialWorkflow = builder.AddWorkflow("science-sequential-workflow", (sp, key) =>
|
|
{
|
|
List<IHostedAgentBuilder> usedAgents = [chemistryAgent, mathsAgent, literatureAgent];
|
|
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.AddWorkflow("nonAgentWorkflow", (sp, key) =>
|
|
{
|
|
List<IHostedAgentBuilder> usedAgents = [pirateAgentBuilder, chemistryAgent];
|
|
var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
|
|
return AgentWorkflowBuilder.BuildSequential(workflowName: key, agents: agents);
|
|
});
|
|
|
|
builder.Services.AddKeyedSingleton("NonAgentAndNonmatchingDINameWorkflow", (sp, key) =>
|
|
{
|
|
List<IHostedAgentBuilder> usedAgents = [pirateAgentBuilder, chemistryAgent];
|
|
var agents = usedAgents.Select(ab => sp.GetRequiredKeyedService<AIAgent>(ab.Name));
|
|
return AgentWorkflowBuilder.BuildSequential(workflowName: "random-name", agents: agents);
|
|
});
|
|
|
|
builder.Services.AddSingleton<AIAgent>(sp =>
|
|
{
|
|
var chatClient = sp.GetRequiredKeyedService<IChatClient>("chat-model");
|
|
return new ChatClientAgent(chatClient, name: "default-agent", instructions: "you are a default agent.");
|
|
});
|
|
|
|
builder.Services.AddKeyedSingleton<AIAgent>("my-di-nonmatching-agent", (sp, name) =>
|
|
{
|
|
var chatClient = sp.GetRequiredKeyedService<IChatClient>("chat-model");
|
|
return new ChatClientAgent(
|
|
chatClient,
|
|
name: "some-random-name", // demonstrating registration can be different for DI and actual agent
|
|
instructions: "you are a dependency inject agent. Tell me all about dependency injection.");
|
|
});
|
|
|
|
builder.Services.AddKeyedSingleton<AIAgent>("my-di-matchingname-agent", (sp, name) =>
|
|
{
|
|
if (name is not string nameStr)
|
|
{
|
|
throw new NotSupportedException("Name should be passed as a key");
|
|
}
|
|
|
|
var chatClient = sp.GetRequiredKeyedService<IChatClient>("chat-model");
|
|
return new ChatClientAgent(
|
|
chatClient,
|
|
name: nameStr, // demonstrating registration with the same name
|
|
instructions: "you are a dependency inject agent. Tell me all about dependency injection.");
|
|
});
|
|
|
|
pirateAgentBuilder.AddA2AServer();
|
|
knightsKnavesAgentBuilder.AddA2AServer();
|
|
|
|
var app = builder.Build();
|
|
|
|
app.MapOpenApi();
|
|
app.UseSwaggerUI(options => options.SwaggerEndpoint("/openapi/v1.json", "Agents API"));
|
|
|
|
// Configure the HTTP request pipeline.
|
|
app.UseExceptionHandler();
|
|
|
|
// Expose A2A servers over HTTP with JSON payloads
|
|
app.MapA2AHttpJson(pirateAgentBuilder, path: "/a2a/pirate");
|
|
app.MapA2AHttpJson(knightsKnavesAgentBuilder, path: "/a2a/knights-and-knaves");
|
|
|
|
app.MapDevUI();
|
|
|
|
app.MapOpenAIResponses();
|
|
app.MapOpenAIConversations();
|
|
|
|
app.MapOpenAIChatCompletions(pirateAgentBuilder);
|
|
app.MapOpenAIChatCompletions(knightsKnavesAgentBuilder);
|
|
|
|
// Map the agents HTTP endpoints
|
|
app.MapAgentDiscovery("/agents");
|
|
|
|
app.MapDefaultEndpoints();
|
|
app.Run();
|