Building Real-Time AI Applications with SignalR and .NET

Summary: This post explores how to build real-time AI applications using SignalR and .NET. Learn how to create interactive applications that leverage AI capabilities while providing immediate feedback and updates to users through real-time communication.

Introduction

Real-time communication has become an essential component of modern web applications, enabling immediate updates, interactive experiences, and collaborative features. When combined with artificial intelligence, real-time capabilities open up exciting possibilities for creating responsive, intelligent applications that can process and respond to data as it happens.

In this post, we’ll explore how to build real-time AI applications using SignalR and .NET. We’ll cover everything from setting up the real-time infrastructure to integrating various AI services and creating interactive user experiences. By the end of this article, you’ll have the knowledge to create applications that leverage AI capabilities while providing immediate feedback and updates to users through real-time communication.

Understanding Real-Time AI Applications

Before diving into implementation, let’s understand what real-time AI applications are and why they’re valuable.

What are Real-Time AI Applications?

Real-time AI applications combine two key technologies:

  1. Real-Time Communication: Technologies that enable immediate data transmission between clients and servers, allowing for instant updates without requiring page refreshes or manual polling.
  2. Artificial Intelligence: Systems that can analyze data, make predictions, generate content, or perform other intelligent tasks based on algorithms and models.

When combined, these technologies create applications that can:

  • Process data as it’s generated
  • Provide immediate AI-powered insights or responses
  • Enable collaborative AI experiences
  • Create interactive, intelligent interfaces

Common Use Cases for Real-Time AI

Real-time AI applications are valuable in many scenarios:

  • Live Analytics Dashboards: Visualizing AI-processed data in real-time
  • Collaborative AI Tools: Multiple users working with AI systems simultaneously
  • Interactive AI Assistants: Chatbots or assistants that respond immediately
  • AI-Powered Monitoring: Systems that detect and alert on anomalies in real-time
  • Live Content Generation: Creating or transforming content as users interact
  • Multiplayer AI Games: Games where AI responds to multiple players in real-time
  • Intelligent IoT Applications: Processing and responding to sensor data immediately

Benefits of Real-Time AI

Combining real-time capabilities with AI offers several advantages:

  • Improved User Experience: Immediate feedback creates more engaging experiences
  • Faster Decision Making: Real-time insights enable quicker responses
  • Collaborative Intelligence: Multiple users can benefit from AI simultaneously
  • Adaptive Systems: Applications can adjust to changing conditions immediately
  • Continuous Learning: Systems can learn and improve from ongoing interactions

Setting Up the Development Environment

Let’s start by setting up our development environment for building real-time AI applications.

Prerequisites

To follow along with this tutorial, you’ll need:

  • Visual Studio 2022 or Visual Studio Code
  • .NET 8 SDK
  • An Azure subscription (for Azure OpenAI Service)
  • Node.js and npm (for the client-side application)

Creating a New Project

Let’s create a new ASP.NET Core Web Application with SignalR:

bash

dotnet new webapp -n RealTimeAI
cd RealTimeAI

Installing Required Packages

Add the necessary packages to your project:

bash

dotnet add package Microsoft.AspNetCore.SignalR.Client
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.ML
dotnet add package System.Linq.Async

Setting Up Configuration

Create or update the appsettings.json file to include your AI service configuration:

json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AzureOpenAI": {
    "Endpoint": "https://your-openai-service.openai.azure.com/",
    "Key": "your-openai-api-key",
    "DeploymentName": "your-gpt-4-deployment"
  }
}

Implementing the SignalR Hub

The SignalR Hub is the central component that enables real-time communication between clients and the server. Let’s create a hub for our AI application:

csharp

// Hubs/AIHub.cs
using Microsoft.AspNetCore.SignalR;
using RealTimeAI.Services;

namespace RealTimeAI.Hubs
{
    public class AIHub : Hub
    {
        private readonly AIService _aiService;
        private readonly ILogger<AIHub> _logger;

        public AIHub(AIService aiService, ILogger<AIHub> logger )
        {
            _aiService = aiService;
            _logger = logger;
        }

        public async Task SendMessage(string user, string message)
        {
            _logger.LogInformation("Message received from {User}: {Message}", user, message);
            
            // Broadcast the message to all clients
            await Clients.All.SendAsync("ReceiveMessage", user, message);
            
            // Process with AI and send response
            try
            {
                // Notify clients that AI is processing
                await Clients.Caller.SendAsync("AIProcessing", true);
                
                // Get AI response
                var response = await _aiService.GetCompletionAsync(message);
                
                // Send AI response to all clients
                await Clients.All.SendAsync("ReceiveAIResponse", "AI Assistant", response);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing AI response");
                await Clients.Caller.SendAsync("AIError", ex.Message);
            }
            finally
            {
                // Notify clients that AI processing is complete
                await Clients.Caller.SendAsync("AIProcessing", false);
            }
        }

        public async Task JoinGroup(string groupName)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
            await Clients.Group(groupName).SendAsync("UserJoined", $"User {Context.ConnectionId} joined the group");
        }

        public async Task LeaveGroup(string groupName)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
            await Clients.Group(groupName).SendAsync("UserLeft", $"User {Context.ConnectionId} left the group");
        }

        public async Task StartStreamingCompletion(string prompt)
        {
            _logger.LogInformation("Starting streaming completion for prompt: {Prompt}", prompt);
            
            try
            {
                // Notify clients that streaming is starting
                await Clients.Caller.SendAsync("StreamingStarted");
                
                // Stream AI response
                await foreach (var chunk in _aiService.GetStreamingCompletionAsync(prompt))
                {
                    await Clients.Caller.SendAsync("ReceiveStreamingChunk", chunk);
                }
                
                // Notify clients that streaming is complete
                await Clients.Caller.SendAsync("StreamingCompleted");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error streaming AI response");
                await Clients.Caller.SendAsync("AIError", ex.Message);
            }
        }

        public override async Task OnConnectedAsync()
        {
            _logger.LogInformation("Client connected: {ConnectionId}", Context.ConnectionId);
            await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception exception)
        {
            _logger.LogInformation("Client disconnected: {ConnectionId}", Context.ConnectionId);
            await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
            await base.OnDisconnectedAsync(exception);
        }
    }
}

Creating AI Services

Now, let’s implement the AI services that our SignalR hub will use:

csharp

// Services/AIService.cs
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.Options;

namespace RealTimeAI.Services
{
    public class AIService
    {
        private readonly OpenAIClient _client;
        private readonly string _deploymentName;
        private readonly ILogger<AIService> _logger;

        public AIService(IConfiguration configuration, ILogger<AIService> logger)
        {
            _logger = logger;
            
            var endpoint = configuration["AzureOpenAI:Endpoint"];
            var key = configuration["AzureOpenAI:Key"];
            _deploymentName = configuration["AzureOpenAI:DeploymentName"];
            
            _client = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(key));
        }

        public async Task<string> GetCompletionAsync(string prompt)
        {
            _logger.LogInformation("Getting completion for prompt: {Prompt}", prompt);
            
            try
            {
                var chatCompletionsOptions = new ChatCompletionsOptions
                {
                    Messages =
                    {
                        new ChatMessage(ChatRole.System, "You are a helpful AI assistant."),
                        new ChatMessage(ChatRole.User, prompt)
                    },
                    Temperature = 0.7f,
                    MaxTokens = 800
                };
                
                var response = await _client.GetChatCompletionsAsync(_deploymentName, chatCompletionsOptions);
                var completion = response.Value;
                
                return completion.Choices[0].Message.Content;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting completion");
                throw;
            }
        }

        public async IAsyncEnumerable<string> GetStreamingCompletionAsync(string prompt)
        {
            _logger.LogInformation("Getting streaming completion for prompt: {Prompt}", prompt);
            
            try
            {
                var chatCompletionsOptions = new ChatCompletionsOptions
                {
                    Messages =
                    {
                        new ChatMessage(ChatRole.System, "You are a helpful AI assistant."),
                        new ChatMessage(ChatRole.User, prompt)
                    },
                    Temperature = 0.7f,
                    MaxTokens = 800
                };
                
                var response = await _client.GetChatCompletionsStreamingAsync(_deploymentName, chatCompletionsOptions);
                
                await foreach (var update in response)
                {
                    if (update.ContentUpdate != null)
                    {
                        yield return update.ContentUpdate;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting streaming completion");
                throw;
            }
        }

        public async Task<ImageGenerationResponse> GenerateImageAsync(string prompt)
        {
            _logger.LogInformation("Generating image for prompt: {Prompt}", prompt);
            
            try
            {
                var imageGenerationOptions = new ImageGenerationOptions
                {
                    Prompt = prompt,
                    Size = ImageSize.Size1024x1024,
                    Quality = ImageGenerationQuality.Standard,
                    ResponseFormat = ImageGenerationResponseFormat.Url
                };
                
                var response = await _client.GetImageGenerationsAsync(imageGenerationOptions);
                var result = response.Value;
                
                return new ImageGenerationResponse
                {
                    Url = result.Data[0].Url.ToString(),
                    Prompt = prompt
                };
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error generating image");
                throw;
            }
        }
    }

    public class ImageGenerationResponse
    {
        public string Url { get; set; }
        public string Prompt { get; set; }
    }
}

Configuring the Application

Now, let’s configure our application to use SignalR and our AI services:

csharp

// Program.cs
using RealTimeAI.Hubs;
using RealTimeAI.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
builder.Services.AddSingleton<AIService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapHub<AIHub>("/aihub");

app.Run();