Skip to main content
You are viewing help content for pre-release software. This document and the features it describes are subject to change.
All docs
V26.1
  • AI Agent Integration in WinForms AI Chat Control

    • 6 minutes to read

    AI agents add an orchestration layer above the underlying chat client. An agent evaluates incoming messages, applies predefined instructions, and resolves required actions.

    The DevExpress WinForms AI Chat Control 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 agent-driven logic instead of direct IChatClient calls.

    How It Works

    The AI Chat Control 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:

    Install a NuGet package for an AI service provider (for example, Azure.AI.OpenAI (2.9.0-beta.1 or later)).

    Add the WinForms AI Chat Control to your application.

    Create and Configure an AI Agent

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

    using DevExpress.AIIntegration;
    using DevExpress.AIIntegration.Agents;
    using DevExpress.AIIntegration.Chat;
    using Azure.AI.OpenAI;
    using Microsoft.Agents.AI;
    using Microsoft.Extensions.AI;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.ClientModel;
    using System.Windows.Forms;
    
    namespace DXWinFormsAIChatAgent {
        internal static class Program {
            [STAThread]
            static void Main() {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
    
                // Create an Azure OpenAI chat client.
                IChatClient chatClient = new AzureOpenAIClient(
                    new Uri(AzureOpenAIEndpoint),
                    new ApiKeyCredential(AzureOpenAIKey))
                        .GetChatClient(ModelId)
                        .AsIChatClient();
    
                // Create an AI agent and define instructions.
                AIAgent agent = chatClient.AsAIAgent(
                    name: "TaskAgent",
                    instructions: 
                      "You are a task-oriented AI agent. Break down user requests into clear steps. " +
                      "Execute tasks in a logical order. " +
                      "Return structured results. " +
                      "Do not include unnecessary explanations unless explicitly requested."
                    );
    
                // Create a service collection to register the AI agent.
                var serviceCollection = new ServiceCollection();
    
                // Register the agent.
                serviceCollection.AddSingleton<IChatResponseProvider>(agent.AsIChatResponseProvider());
    
                serviceCollection.AddDevExpressAIDesktop();
                Application.Run(new Form1());
            }
    
            static string AzureOpenAIEndpoint { get {
                return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); }
            }
            static string AzureOpenAIKey { get {
                return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); }
             }
            static string ModelId { get { return "gpt-5.2"; } }
        }
    }
    

    The following screenshot illustrates the result:

    AI Agent - WinForms AI Chat Control, DevExpress

    Manage and Persist Conversation History

    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.
    using DevExpress.AIIntegration;
    using DevExpress.AIIntegration.Agents;
    using DevExpress.AIIntegration.Chat;
    using Azure.AI.OpenAI;
    using Microsoft.Agents.AI;
    using Microsoft.Extensions.AI;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.ClientModel;
    using System.IO;
    using System.Text;
    using System.Text.Json;
    using System.Windows.Forms;
    
    namespace DXWinFormsAIChatAgents {
        internal static class Program {
            internal const string StatefulChatResponseProviderServiceKey = "ChatResponseProvider-Agent-stateful";
    
            [STAThread]
            static void Main() {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
    
                // Create an Azure OpenAI chat client.
                IChatClient chatClient = new AzureOpenAIClient(
                    new Uri(AzureOpenAIEndpoint),
                    new ApiKeyCredential(AzureOpenAIKey))
                        .GetChatClient(ModelId)
                        .AsIChatClient();
    
                AIAgent agent = chatClient.AsAIAgent(
                    name: "TaskAgent",
                    instructions:
                      "You are a task-oriented AI agent. Break down user requests into clear steps. " +
                      "Execute tasks in a logical order. " +
                      "Return structured results. " +
                      "Do not include unnecessary explanations unless explicitly requested."
                    );
    
                // Create a stateful store to manage the agent session between application runs.
                AIAgentStatefulStore statefulStore = new AIAgentStatefulStore(agent);
    
                // Create a service collection to register the AI agent.
                var serviceCollection = new ServiceCollection();
    
                serviceCollection.AddChatClient(chatClient);
                serviceCollection.AddKeyedSingleton<IChatResponseProvider>(
                    StatefulChatResponseProviderServiceKey, (sp, _) => {
                        return agent.AsIChatResponseProvider(statefulStore.GetOrRestoreSession());
                });
    
                Application.Run(new AIAgentStatefulForm(statefulStore));
            }
    
            static string AzureOpenAIEndpoint { get { 
                return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); }
            }
            static string AzureOpenAIKey { get {
                return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); }
            }
            static string ModelId { get { return "gpt-4o-mini"; } }
        }
    }
    
    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;
            }
        }
    }
    
    using DevExpress.AIIntegration.Blazor.Chat;
    using DevExpress.XtraEditors;
    using Ganss.Xss;
    using Microsoft.AspNetCore.Components;
    using Microsoft.Extensions.AI;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.IO;
    using System.Linq;
    using System.Text.Json;
    
    namespace DXWinFormsAIChatAgents {
        public partial class AIAgentStatefulForm : XtraForm {
            readonly HtmlSanitizer sanitizer;
            readonly AIAgentStatefulStore stateStore;
            readonly string statefulMessagesFilePath;
    
            public AIAgentStatefulForm(AIAgentStatefulStore stateStore) {
                InitializeComponent();
    
                this.stateStore = stateStore;
                statefulMessagesFilePath = Path.Combine(
                    AppDomain.CurrentDomain.BaseDirectory,
                    "stateful_messages.json");
            }
    
            protected override void OnClosing(CancelEventArgs e) {
                base.OnClosing(e);
                // Persist messages and agent session state before the form closes.
                SaveMessages(aiChatControl1.SaveMessages());
                stateStore.SaveSession();
            }
    
            protected override void OnLoad(EventArgs e) {
                base.OnLoad(e);
                // Restore persisted messages when the form opens.
                List<BlazorChatMessage> messages = LoadMessages();
                if (messages.Count == 0)
                    return;
    
                aiChatControl1.LoadMessages(messages);
            }
    
            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.Content,
                        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);
                }
            }
        }
    }
    

    The following animation illustrates the result. The chat control saves the conversation state on application close and restores chat messages on restart.

    Manage Conversation History - WinForms AI Chat Control, DevExpress

    The following Microsoft Agent Framework examples create an agent that uses memory to store and reuse conversation data:

    See Also