Skip to main content
All docs
V26.1
  • AI Agent Integration

    • 5 minutes to read

    AI agents add a layer above the underlying chat client. An agent evaluates incoming messages, applies predefined instructions, and performs the corresponding actions.

    The DevExpress Blazor AI Chat supports AI agent integration. You can bind the chat control to an IChatResponseProvider implementation backed by an AI agent. The control then delegates response generation to the agent instead of calling IChatClient directly.

    How It Works

    The DxAIChat component routes user input to the associated AI agent. The agent processes the input and executes orchestration logic through the Microsoft Agent Framework APIs. After execution completes, the agent returns a response payload to the UI for display.

    Prerequisites

    Install the following NuGet packages:

    Create and Configure an AI Agent

    Create an AI agent from an existing chat client. Define clear, task-specific instructions to control agent behavior.

    using Azure.AI.OpenAI;
    using DevExpress.AIIntegration.Agents;
    using Microsoft.Agents.AI;
    using Microsoft.Extensions.AI;
    using System.ClientModel;
    
    /* ... */
    
    // Create a chat client
    var chatClient = new AzureOpenAIClient(azureOpenAiEndpoint, azureOpenAiKey)
        .GetChatClient(azureOpenAiModel)
        .AsIChatClient();
    // Create an AI agent with specific instructions
    AIAgent agent = chatClient.AsAIAgent(
        name: "PoeticAgent",
        instructions: "You are a poetic assistant. Answer every query in rhyming verses."
    );
    var builder = WebApplication.CreateBuilder(args);
    // Register the agent as a default AI service
    builder.Services.AddSingleton(agent.AsIChatResponseProvider());
    builder.Services.AddDevExpressAI();
    

    Display Agent Responses in the Chat

    Add the DxAIChat component to the page.

    <DxAIChat />
    

    AI Agent Integration

    Persist Conversation History

    Use this approach when users need to continue an existing conversation after the application restarts. Persist both chat messages and the underlying agent session to keep the visible chat history and the agent’s internal context synchronized.

    The following example persists chat message history to a JSON file and restores it on application startup.

    The example implements the following tasks:

    • Defines an AIAgentStatefulStore helper class that serializes and deserializes the AgentSession object to a JSON file.
    • Registers the agent in the service collection and restores the saved session during first access.
    • Overrides OnClosing to persist chat messages and agent session state to disk before the application exits.
    • Overrides OnLoad to load saved messages from disk and restore the conversation in the chat control.

    Stateful Store Manager

    Create a class that manages the persistence of an agent’s session state between application runs.

    public class AIAgentStatefulStore
    {
        readonly AIAgent agent;
        // Persists the agent session state between application runs
        readonly string statefulSessionFilePath = Path.Combine(
            AppDomain.CurrentDomain.BaseDirectory,
            "stateful_session.json");
        AgentSession statefulSession = null;
    
        internal AIAgentStatefulStore(AIAgent agent) {
            ArgumentNullException.ThrowIfNull(agent);
            this.agent = agent;
        }
    
        internal AgentSession GetOrRestoreSession() {
            // Return the cached session if already initialized
            if(statefulSession != null)
                return statefulSession;
            // Load from disk if a saved session exists; otherwise create a new session
            JsonElement? storedSession = LoadJsonElement(statefulSessionFilePath);
            statefulSession = storedSession.HasValue
                ? agent.DeserializeSessionAsync(storedSession.Value).AsTask().GetAwaiter().GetResult()
                : agent.CreateSessionAsync().AsTask().GetAwaiter().GetResult();
    
            return statefulSession;
        }
    
        internal void SaveSession() {
            if(statefulSession == null)
                return;
            // Serialize the agent session to JSON and persist it to disk
            JsonElement sessionJson = agent.SerializeSessionAsync(statefulSession).GetAwaiter().GetResult();
            SaveJsonElement(sessionJson, statefulSessionFilePath);
        }
    
        static void SaveJsonElement(JsonElement element, string filePath) {
            string json = element.GetRawText();
            string tmp = filePath + ".tmp";
    
            File.WriteAllText(tmp, json, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
    
            if(File.Exists(filePath))
                File.Replace(tmp, filePath, destinationBackupFileName: null);
            else
                File.Move(tmp, filePath);
        }
    
        static JsonElement? LoadJsonElement(string filePath) {
            if(!File.Exists(filePath))
                return null;
    
            string json = File.ReadAllText(filePath);
            try {
                using var doc = JsonDocument.Parse(json);
                return doc.RootElement.Clone();
            }
            catch(JsonException) {
                return null;
            }
        }
    }
    

    Create and Configure a Stateful AI Agent

    Register an AI agent based on an existing chat client and create a stateful store to manage the agent session between application runs:

    // Create a chat client
    var chatClient = new AzureOpenAIClient(azureOpenAiEndpoint, azureOpenAiKey)
        .GetChatClient(azureOpenAiModel)
        .AsIChatClient();
    // Create an AI agent with specific instructions
    AIAgent agent = chatClient.AsAIAgent(
        name: "PoeticAgent",
        instructions: "You are a poetic assistant. Answer every query in rhyming verses."
    );
    // Create a stateful store to manage the agent session between application runs
    var statefulStore = new AIAgentStatefulStore(agent);
    var builder = WebApplication.CreateBuilder(args);
    // Register the agent as a default AI service
    builder.Services.AddSingleton(agent.AsIChatResponseProvider(statefulStore.GetOrRestoreSession()));
    // Register the store so pages can inject it to call SaveSession
    builder.Services.AddSingleton(statefulStore);
    // Add DevExpress Blazor and AI services
    builder.Services.AddDevExpressAI();
    builder.Services.AddDevExpressBlazor();
    

    Add an AI Chat Component

    Add a DxAIChat component to the page and handle its lifecycle events to restore saved messages, persist new responses, and save the agent session when the component is disposed.

    <DxAIChat Initialized="ChatInitialized" ResponseReceived="ResponseReceived" />
    
    @code {
        static readonly string statefulMessagesFilePath = Path.Combine(
            AppDomain.CurrentDomain.BaseDirectory,
            @"C:\Resources\stateful_messages.json");
    
        IAIChat chat;
    
        void ChatInitialized(IAIChat chat) {
            this.chat = chat;
            List<BlazorChatMessage> messages = LoadMessages();
            if(messages.Count > 0)
                chat.LoadMessages(messages);
        }
    
        void ResponseReceived(ResponseReceivedEventArgs args) {
            SaveMessages(chat.SaveMessages());
        }
    
        public ValueTask DisposeAsync() {
            StateStore.SaveSession();
            return ValueTask.CompletedTask;
        }
    
        void SaveMessages(IEnumerable<BlazorChatMessage> messages) {
            List<StoredChatMessage> storedMessages = messages
                .Select(StoredChatMessage.Create)
                .ToList();
    
            using FileStream stream = File.Create(statefulMessagesFilePath);
            JsonSerializer.Serialize(stream, storedMessages);
        }
    
        List<BlazorChatMessage> LoadMessages() {
            if(!File.Exists(statefulMessagesFilePath))
                return new List<BlazorChatMessage>();
    
            try {
                using FileStream stream = File.OpenRead(statefulMessagesFilePath);
                List<StoredChatMessage> storedMessages = JsonSerializer
                    .Deserialize<List<StoredChatMessage>>(stream)
                    ?? new List<StoredChatMessage>();
                return storedMessages.Select(x => x.ToBlazorChatMessage()).ToList();
            }
            catch {
                return new List<BlazorChatMessage>();
            }
        }
    
        sealed record StoredChatMessage(ChatRole Role, string Content, List<StoredChatFile> Files, bool Typing)
        {
            internal static StoredChatMessage Create(BlazorChatMessage message) {
                ArgumentNullException.ThrowIfNull(message);
                return new StoredChatMessage(
                    ToChatRole(message.Role),
                    message.Text,
                    message.Files.Select(StoredChatFile.Create).ToList(),
                    message.Typing);
            }
    
            internal BlazorChatMessage ToBlazorChatMessage() {
                BlazorChatMessage message = new(
                    Role,
                    Content,
                    Files.Select(x => x.ToUploadFileInfo()).ToList()) {
                    Typing = Typing
                };
                return message;
            }
    
            static ChatRole ToChatRole(ChatMessageRole role) {
                return role switch {
                    ChatMessageRole.User => ChatRole.User,
                    ChatMessageRole.Assistant => ChatRole.Assistant,
                    ChatMessageRole.System => ChatRole.System,
                    ChatMessageRole.Tool => ChatRole.Tool,
                    _ => throw new InvalidOperationException($"Unsupported chat role '{role}'.")
                };
            }
        }
    
        sealed record StoredChatFile(string Name, string Type, decimal Size, byte[] Data, string Guid)
        {
            internal static StoredChatFile Create(AIChatUploadFileInfo file) {
                ArgumentNullException.ThrowIfNull(file);
                return new StoredChatFile(file.Name, file.Type, file.Size, file.Data.ToArray(), file.Guid);
            }
    
            internal AIChatUploadFileInfo ToUploadFileInfo() {
                return new AIChatUploadFileInfo(Name, Type, Size, new ReadOnlyMemory<byte>(Data), Guid);
            }
        }
    }