Blazor Ai Chat A2A Mode
39Integrates the DevExpress Blazor AI Chat component with AI agents that conform to the Agent2Agent (A2A) protocol.
Getting Started
README
Blazor AI Chat — Communicate with Agents Using the Agent2Agent (A2A) Protocol
This example integrates the DevExpress Blazor AI Chat component with AI agents that conform to the Agent2Agent (A2A) protocol. These agents do not implement real inter-agent communication.
Our implementation adheres to the following:
- A2A Protocol Compliance: Agents conform to the A2A protocol specification (A2A .NET SDK).
- Distributed Agent Architecture: Hosts A2A protocol-compliant AI agents.
- Dynamic Agent Switching: Users can switch agents in real time.
In this specific example, you can:
- Select an AI agent from the combo box (Poem, Shakespearean Style, Task, Researcher).
- Forward messages from one agent to another (manually). For example, you can copy a poem generated by the Poem Agent, switch to the Shakespearean Style Agent, send it in the chat, and receive a stylized version.
- Simulate Task and Researcher agents.
[!NOTE]
ResearcherAgent(source code) is based on an official Microsoft sample. This agent can receive user input, run through a simulated reasoning process, and return structured responses.
Prerequisites
- .NET 8+ SDK
- Visual Studio 2022, JetBrains Rider, or Visual Studio Code with C# extension
Get Started
To run the example, configure Visual Studio to start multiple projects in the correct sequence:
- In Solution Explorer, right-click the solution and select Configure Startup Projects…
- In the dialog, select Multiple startup projects.
- Arrange projects in the following order:
- PoemAgentServer
- ShakespeareanStyleAgentServer
- TaskAgentServer
- ResearcherAgentServer
- DXBlazorChatA2ASample
- Set the action for each project to Start. Set the debug target to
httporhttpsas needed. - Click OK to save the configuration.

[!TIP] Use the pre-built launch profile (.slnLaunch) to start all projects with a single click.
[!Note] We use the following versions of Microsoft AI packages in our
v25.2.2+source code:
Microsoft.Extensions.AI| 9.7.1Microsoft.Extensions.AI.OpenAI| 9.7.1-preview.1.25365.4Azure.AI.OpenAI| 2.2.0-beta.5We do not guarantee compatibility or correct operation with higher versions. Refer to the following announcement for additional information: DevExpress.AIIntegration moves to a stable version.
Project Structure
The solution includes the following projects:
- A2A Server: A shared library that includes base classes for agent implementations. You can extend base APIs to create custom agents. Custom agents can then be invoked directly from the DevExpress Blazor Chat through the A2A protocol.
- PoemAgentServer: A web app that hosts an agent designed to generate poems.
- ResearcherAgentServer: A web app that hosts an agent that executes research-oriented tasks.
- ShakespeareanStyleAgentServer: A web app that hosts an agent that rewrites or transforms text into Shakespearean (Elizabethan) style.
- TaskAgentServer: A web app that hosts an agent designed to processes task-oriented requests (such as instructions, reminders, or structured actions).
- Blazor Server Application: Blazor Server app with the DevExpress AI Chat component. Connects to AI agents through
IChatClientinstances. Users can interact with agents within a single chat interface.
📁 Project Structure
├── 🖥️ A2AServer.Shared/ # Shared library for all agent servers
│ ├── Program.cs # Agent server configuration
│ ├── Agents/
│ │ └── Base/ # Abstract/base classes for agent implementations
│ └── AzureOpenAIServiceSettings.cs # Configuration model for Azure OpenAI service
│
├── 🖥️ PoemAgentServer/ # Agent server that generates poems
│ ├── Program.cs
│ └── Agents/
│ └── PoemAgent.cs
│
├── 🖥️ ResearcherAgentServer/ # Agent server that executes research tasks
│ ├── Program.cs
│ └── Agents/
│ └── ResearcherAgent.cs
│
├── 🖥️ ShakespeareanStyleAgentServer/ # Agent server that rewrites text in Shakespearean style
│ ├── Program.cs
│ └── Agents/
│ └── ShakespeareanStyleAgent.cs
│
├── 🖥️ TaskAgentServer/ # Agent server that handles general task execution
│ ├── Program.cs
│ └── Agents/
│ └── TaskAgent.cs
│
└── 🌐 DXBlazorChatA2ASample/ # Blazor application
├── Components/Pages/Chat.razor # Chat page with DevExpress DxAIChat component
├── Agents/ # A2A client adapters for agent communication
│ ├── AgentsEndpoints.cs # Agent endpoint configuration
│ ├── MessageAgentChatClient.cs # Client adapter for message-based agent interaction
│ └── TaskAgentChatClient.cs # Client adapter for task agent interaction
└── Services/ # Application services
Setup and Configuration
Configure AI Service Provider
This example uses Azure OpenAI. Specify the endpoint, key, and model name in appsettings.json across all projects:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AzureOpenAISettings": {
"Endpoint": "https://your-instance.openai.azure.com/",
"Key": "your-api-key-here",
"DeploymentName": "ai-model-name"
},
"AllowedHosts": "*"
}
The implementation supports the following providers:
- Azure OpenAI: Set up an Azure OpenAI Service
- OpenAI: Create an OpenAI account and obtain your API key
- Local AI: Use Ollama for open/self-hosted models
Implementation Details
A2A Server (Shared)
Defines base classes for AI agents that adnere the A2A protocol and contains the configuration model (AzureOpenAIServiceSettings).
Agent Servers
A2A servers host AI agents that support the A2A protocol:
// PoemAgentServer.Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Initialize the task manager for the AI agent.
TaskManager taskManager = new TaskManager();
// Create and configure an Azure OpenAI chat client.
var openAiServiceSettings = builder.Configuration.GetSection("AzureOpenAISettings").Get<AzureOpenAIServiceSettings>();
var chatClient = new AzureOpenAIClient(
new Uri(openAiServiceSettings.Endpoint),
new AzureKeyCredential(openAiServiceSettings.Key))
.GetChatClient(openAiServiceSettings.DeploymentName)
.AsIChatClient();
// Initialize the Poem AI agent.
var poemAgent = new PoemAgent(chatClient, taskManager);
app.UseHttpsRedirection();
// Map the A2A endpoint.
app.MapA2A(taskManager, "/agent");
app.Run();
Agent Implementation
The example implements the following hierarchical agent architecture with a clear separation of concerns:
BaseAgent (Abstract)
├── BaseMessageAgent (Message Processing)
│ ├── PoemAgent
│ └── ShakespeareanStyleAgent
├── BaseTaskAgent (Task Management)
│ └── TaskAgent
└── ResearcherAgent
Base Agent Classes
The BaseAgent abstract class implements core functionality for AI agents:
public abstract class BaseAgent {
private IChatClient? _innerChatClient;
protected BaseAgent(IChatClient chatClient, ITaskManager taskManager) {
InnerChatClient = chatClient ?? throw new ArgumentNullException(nameof(chatClient));
TaskManager = taskManager ?? throw new ArgumentNullException(nameof(taskManager));
}
protected IChatClient? InnerChatClient {
get => _innerChatClient;
set => _innerChatClient = value;
}
protected ITaskManager TaskManager { get; }
}
The BaseMessageAgent abstract class is a base class for AI agents that process messages:
public abstract class BaseMessageAgent : BaseAgent {
public BaseMessageAgent(IChatClient chatClient, ITaskManager taskManager) : base(chatClient, taskManager) {
}
public abstract Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken);
public abstract Task<A2AResponse> ProcessMessageAsync(MessageSendParams messageSendParams, CancellationToken ct);
}
The BaseTaskAgent abstract class manages and tracks task lifecycles. It defines hooks that create/update tasks and exposes the agent's capabilities through the GetAgentCardAsync method for external discovery or integration.
public abstract class BaseTaskAgent : BaseAgent {
public BaseTaskAgent(IChatClient chatClient, ITaskManager taskManager) : base(chatClient, taskManager) {
}
// Invoked when a new task is created. Implement to define how the agent initializes or processes tasks.
protected abstract Task OnTaskCreated(AgentTask task, CancellationToken token);
// Invoked when an existing task is updated. Implement to handle state transitions or progress updates.
protected abstract Task OnTaskUpdated(AgentTask task, CancellationToken token);
protected abstract Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken);
}
AI Agents
The following AI agents extend the base agent architecture. They implement message-based and task-based processing using the A2A protocol.
PoemAgent: Generates poems based on user-provided words.ShakespeareanStyleAgent: Rewrites user input into Shakespearean-style prose or verse.TaskAgent: Simulates task creation and state transitions.
DevExpress AI Chat Integration
The DxAIChat.ChatClientServiceKey property associates the chat component with a chat client service. Users can switch agents in real time using the combo box:
<DxAIChat @ref="dxAIChat"
UseStreaming="true"
ChatClientServiceKey="@CurrentServiceKey"
CssClass="chat-container">
...
</DxAIChat>
<DxComboBox Data="@AgentOptions"
TextFieldName="@nameof(AgentOption.Text)"
ValueFieldName="@nameof(AgentOption.Value)"
@bind-Value="@CurrentServiceKey" />
Display Instructions
The EmptyMessageAreaTemplate defines the content displayed when the chat is empty (such as available agents and instructions). This template appears at startup or after the user clears the chat.

<EmptyMessageAreaTemplate>
<div class="empty-message-area">
<h4>🤖 Multi-Agent AI Chat</h4>
<p>Welcome to the Agent2Agent (A2A) communication platform! Select an AI agent to get started.</p>
<div>
<h5>Available agents:</h5>
<ul class="agents-list">
<li><strong>🎭 Poem Agent</strong>: Submit 2-5 keywords, and I'll create a short lyrical poem.</li>
<li><strong>📜 Shakespearean Style Agent</strong>: Turn your text into Shakespearean-style writing.</li>
<li><strong>📋 Task Agent</strong>: Plan and execute task descriptions with step-by-step updates.</li>
<li><strong>🧠 Researcher Agent</strong>: Explore research capabilities (<a href="https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/ResearcherAgent.cs" target="_blank">source</a>).</li>
<li><strong>💬 Standard Chat Client</strong>: Engage in a standard AI conversation.</li>
</ul>
</div>
<div>
<p><strong>Current Agent:</strong> <span class="current-agent-badge">@AgentOptions.FirstOrDefault(x => x.Value == CurrentServiceKey)?.Text</span></p>
</div>
<p class="help-text">
<small>💡 Select an agent from the dropdown list above, then enter a message to get started.</small>
</p>
</div>
</EmptyMessageAreaTemplate>
private void ClearChatHistory() {
if (_dxAIChat != null) {
// Reset chat history (load an empty collection).
_dxAIChat.LoadMessages(new List<BlazorChatMessage>());
}
}
Configure Agent Endpoints
The AgentsEndpoints static class centralizes agent endpoints:
public static class AgentsEndpoints {
public const string PoemAgent = "http://localhost:5003/agent";
public const string ShakespeareanStyleAgent = "http://localhost:5005/agent";
public const string TaskAgent = "http://localhost:5007/agent";
public const string ResearcherAgent = "http://localhost:5009/agent";
// HTTPS alternatives:
// public const string PoemAgent = "https://localhost:5004/agent";
// public const string ShakespeareanStyleAgent = "https://localhost:5006/agent";
// public const string TaskAgent = "https://localhost:5008/agent";
// public const string ResearcherAgent = "https://localhost:5010/agent";
}
Register AI Agents
Register A2A chat clients as keyed services to seamlessly switch agents (Program.cs):
builder.Services.AddKeyedScoped<IChatClient>(AgentsEndpoints.PoemAgent, (provider, key) => {
var a2aclient = new A2AClient(new Uri(AgentsEndpoints.PoemAgent));
return new MessageAgentChatClient(chatClient, a2aclient, AgentsEndpoints.PoemAgent);
});
builder.Services.AddKeyedScoped<IChatClient>(AgentsEndpoints.ShakespeareanStyleAgent, (provider, key) => {
var a2aclient = new A2AClient(new Uri(AgentsEndpoints.ShakespeareanStyleAgent));
return new MessageAgentChatClient(chatClient, a2aclient, AgentsEndpoints.ShakespeareanStyleAgent);
});
builder.Services.AddKeyedScoped<IChatClient>(AgentsEndpoints.TaskAgent, (provider, key) => {
var a2aclient = new A2AClient(new Uri(AgentsEndpoints.TaskAgent));
return new TaskAgentChatClient(chatClient, a2aclient, AgentsEndpoints.TaskAgent);
});
builder.Services.AddKeyedScoped<IChatClient>(AgentsEndpoints.ResearcherAgent, (provider, key) => {
var a2aclient = new A2AClient(new Uri(AgentsEndpoints.ResearcherAgent));
return new TaskAgentChatClient(chatClient, a2aclient, AgentsEndpoints.ResearcherAgent);
});
builder.Services.AddDevExpressAI();
A2A Chat Client Adapter
The MessageAgentChatClient class 'binds' the A2A protocol to the IChatClient interface. This allows agents to seamlessly interact with the DevExpress Blazor AI Chat component:
public sealed class MessageAgentChatClient : DelegatingChatClient {
private A2AClient _agentClient;
private string _contextId;
public MessageAgentChatClient(IChatClient innerClient, A2AClient agentClient, string contextId) : base(innerClient) {
if (innerClient == null)
throw new ArgumentNullException(nameof(innerClient));
_agentClient = agentClient ?? throw new ArgumentNullException(nameof(agentClient));
_contextId = $"{contextId}-{new Guid().ToString()}";
}
// 🎯 KEY METHOD
// Send messages to the AI agent and return the full response as a single ChatResponse.
public override async Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null,
CancellationToken cancellationToken = new CancellationToken()) {
var responses = await GetStreamingResponseAsync(messages, options, cancellationToken).ToArrayAsync(cancellationToken);
return new ChatResponse(new ChatMessage(ChatRole.Assistant, string.Join("", responses.Select(x => x.Text))));
}
// Create an A2A message from the latest user message in the conversation.
private AgentMessage CreateA2AMessage(IEnumerable<ChatMessage> messages) {
return new AgentMessage() {
Role = MessageRole.User,
ContextId = _contextId,
Parts = [new TextPart() { Text = messages.Last().Text }]
};
}
// Send messages to the AI agent and stream the response back as ChatResponseUpdate events.
// The DevExpress DxAIChat calls this method if the chat's UseStreaming option is enabled.
public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = new CancellationToken()) {
MessageSendParams msgSendParams = new MessageSendParams() { Message = CreateA2AMessage(messages) };
await foreach(var item in _agentClient.SendMessageStreamingAsync(msgSendParams, cancellationToken)) {
if(item.Data is AgentMessage message) {
AgentMessage streamingResponse = message;
yield return new ChatResponseUpdate(ChatRole.Assistant, ((TextPart)streamingResponse.Parts[0]).Text);
}
}
}
}
Files to Review
Agent Base Classes
Agents
- ShakespeareanStyleAgent.cs / Program.cs
- PoemAgent.cs / Program.cs
- TaskAgent.cs / Program.cs
- ResearcherAgent.cs / Program.cs
AI Chat Integration
Documentation
- Blazor AI Chat (DevExpress)
- Blazor AI-powered Extensions (DevExpress)
- Microsoft.Extensions.AI Documentation
- IChatClient Interface Reference
- Azure OpenAI Service Documentation
- A2A .NET SDK
More Examples
Demos
Does This Example Address Your Development Requirements/Objectives?
(you will be redirected to DevExpress.com to submit your response)