Skip to content

Repository files navigation

Blazor AI Chat — Multi-Model Chat with Conversation History

This multi-model chat interface allows users to toggle between cloud LLMs and private (local) models within a single environment. Our sample project supports persistent chat threads with history management and automated title generation based on a user's initial prompt.

Multi-Model Chat with Conversation History

The sample app leverages the following DevExpress Blazor components:

Setup and Configuration

To run this sample, configure project dependencies and set up secure authentication for the desired AI service.

Required Packages

We use the following versions of Microsoft AI packages in this project:

NuGet Package Version
Microsoft.Extensions.AI 9.7.1
Microsoft.Extensions.AI.OpenAI 9.7.1-preview.1.25365.4
Microsoft.Extensions.AI.Ollama 9.7.0-preview.1.25356.2
Azure.AI.OpenAI 2.2.0-beta.5

Note

We cannot guarantee compatibility or correct execution with newer versions. Refer to the following announcement for additional information in this regard: DevExpress.AIIntegration references stable versions of Microsoft AI packages.

Register AI Services

Note

DevExpress AI-powered extensions follow the "bring your own key" principle. DevExpress does not offer a REST API and does not ship any built-in LLMs/SLMs. You need an active Azure/Open AI subscription to obtain the REST API endpoint, key, and model deployment name. These variables must be specified at application startup to register AI clients and enable DevExpress AI-powered Extensions in your application.

This example uses the following AI services:

AI Provider Model
Azure OpenAI gpt-4.1
Local Ollama deployment phi4:latest

For security reasons, secrets are stored in the appsettings.json file. Update the following sections with your own credentials:

  • OpenAISettings
    • Endpoint: Your Azure OpenAI endpoint
    • Key: Your Azure OpenAI key
    • DeploymentName: Azure OpenAI model ID
  • OllamaSettings
    • Uri: Local Ollama API endpoint
    • ModelName: Local Ollama model

Note Update appsettings.Development.json to test this example in your local development environment.

The following code in Program.cs retrieves the provider API configuration. Modify this code if you prefer to keep keys in environment variables or User Secrets.

var openAiServiceSettings = builder.Configuration.GetSection("OpenAISettings").Get<OpenAIServiceSettings>();
var ollamaSettings = builder.Configuration.GetSection("OllamaSettings").Get<OllamaSettings>();

// Register individual IChatClient instances as keyed scoped services
builder.Services.AddKeyedScoped<IChatClient>("azure-openai", (_, _) =>
    new AzureOpenAIClient(
            new Uri(openAiServiceSettings.Endpoint),
            new AzureKeyCredential(openAiServiceSettings.Key))
        .GetChatClient(openAiServiceSettings.DeploymentName)
        .AsIChatClient());

builder.Services.AddKeyedScoped<IChatClient>("ollama-phi4", (_, _) =>
    new OllamaChatClient(
        new Uri(ollamaSettings.Uri),
        ollamaSettings.ModelName,
        new HttpClient { Timeout = TimeSpan.FromMinutes(10) }));

// Assemble the composite client from keyed IChatClient services
builder.Services.AddScoped<CompositeChatClient>(provider => {
    var threadStore = provider.GetRequiredService<IChatThreadStore>();
    var titleGenerator = provider.GetRequiredService<IChatThreadTitleGenerator>();

    return new CompositeChatClient(
        threadStore,
        titleGenerator,
        new ChatClientSession(
            provider.GetRequiredKeyedService<IChatClient>("azure-openai"),
            "azure-openai",
            $"Azure Open AI - {openAiServiceSettings.DeploymentName}"),
        new ChatClientSession(
            provider.GetRequiredKeyedService<IChatClient>("ollama-phi4"),
            "ollama-phi4",
            $"Ollama - {ollamaSettings.ModelName}"));
});

Implementation Details

This section introduces key code blocks used in the example and how they work together to deliver a complete AI chat experience.

Layout

This application uses a two-pane layout with a DxSplitter that separates the sidebar and chat pane.

CSS styles that define size and spacing for the splitter, sidebar, and chat component reside in Index.razor.css.

Multi-Model Chat

The application allows users to switch between cloud and local AI providers on-the-fly.

  • Program.cs integrates two named ChatClientSession instances (Azure OpenAI and Ollama) and registers them in the CompositeChatClient object. CompositeChatClient implements the IChatClient interface and forwards chat requests to the current session.
  • Index.razor uses the DxComboBox component bound to CompositeChatClient.AvailableChatClients and updates the selected session. Changing the model preserves existing chat history and continues the conversation with the newly selected model.
  • Each thread stores model selection in the ModelSessionId property. Switching chat threads restores the model selection.

To support streaming responses, our chat implementation uses the default message pipeline without the MessageSent event override.

Dynamic Title Generation

CompositeChatClient intercepts user prompts using GetResponseAsync / GetStreamingResponseAsync. The selected AI model generates an automatic thread title (3–6 words) from the first user prompt in the background.

In case of failure, the first six words of the user message serve as the title.

Conversation History

Each conversation thread is a ChatThread object. It contains a list of messages and metadata that is used in the UI for titles and ordering. Index.razor calls the SaveMessages method before switching threads and LoadMessages when a thread becomes active.

InMemoryChatThreadStore keeps chat history in a dictionary guarded by a lock for thread-safety:

  • Creates new threads with a title, model session ID, and timestamps.
  • Returns ordered threads.
  • Saves messages, updates titles, and updates model session IDs.

Since thread state is stored in memory, all chat history is lost when the application restarts.

Persist Conversation History

To persist chat history when the application restarts, implement IChatThreadStore with a database-backed store (for example, EF Core). Then replace InMemoryChatThreadStore with your implementation in Program.cs:

// Replace with your database-backed implementation
builder.Services.AddSingleton<IChatThreadStore, InMemoryChatThreadStore>();

Files to Review

Documentation

Related Examples

Does This Example Address Your Development Requirements/Objectives?

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

About

Create a multi-model AI chat interface for cloud and on-premise LLM providers.

Topics

Resources

Stars

2 stars

Watchers

30 watching

Forks

Contributors

Languages