Skip to content

Integrates the DevExpress Blazor AI Chat component with AI agents that conform to the Agent2Agent (A2A) protocol.

License

Notifications You must be signed in to change notification settings

DevExpress-Examples/blazor-ai-chat-a2a-mode

Repository files navigation

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:

  1. In Solution Explorer, right-click the solution and select Configure Startup Projects…
  2. In the dialog, select Multiple startup projects.
  3. Arrange projects in the following order:
    • PoemAgentServer
    • ShakespeareanStyleAgentServer
    • TaskAgentServer
    • ResearcherAgentServer
    • DXBlazorChatA2ASample
  4. Set the action for each project to Start. Set the debug target to http or https as needed.
  5. Click OK to save the configuration.

Project Startup 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.1
  • Microsoft.Extensions.AI.OpenAI | 9.7.1-preview.1.25365.4
  • Azure.AI.OpenAI | 2.2.0-beta.5

We do not guarantee compatibility or correct operation with higher versions. Refer to the following announcement for additional information: DevExpress.AIIntegration moves to a stables 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 IChatClient instances. 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:

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.

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.

Welcome Screen - Blazor AI Chat, DevExpress

<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

AI Chat Integration

Documentation

More Examples

Demos

Does this example address your development requirements/objectives?

(you will be redirected to DevExpress.com to submit your response)

About

Integrates the DevExpress Blazor AI Chat component with AI agents that conform to the Agent2Agent (A2A) protocol.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •