DEV Community

Cover image for Building a Multi-Provider AI Chat Application with .NET 10 - A Complete Guide
Rahul Kumar Jha
Rahul Kumar Jha

Posted on • Originally published at github.com

Building a Multi-Provider AI Chat Application with .NET 10 - A Complete Guide

Building a Multi-Provider AI Chat Application with .NET 10 πŸ€–

Have you ever wanted to build an AI application but felt locked into a single provider? What if you could switch between GitHub Models, Azure OpenAI, OpenAI, or even local models with just a configuration change?

In this comprehensive tutorial, I'll show you how I built a production-ready AI chat application using .NET 10 that supports multiple AI providers through a unified interface. By the end, you'll understand:

  • βœ… How to use Microsoft.Extensions.AI for provider-agnostic code
  • βœ… Implementing Provider and Factory design patterns
  • βœ… Secure secrets management with User Secrets
  • βœ… Building interactive console applications
  • βœ… Working with 4 different AI providers

πŸ”— Complete Source Code: github.com/Rahul1994jh/genai_with_dotnet

🎯 The Problem

When building AI applications, you typically face these challenges:

// ❌ The problem: Different APIs for different providers
if (provider == "Azure") {
    var response = await azureClient.GetChatCompletionsAsync(...);
} else if (provider == "OpenAI") {
    var response = await openAiClient.CreateChatCompletionAsync(...);
} else if (provider == "GitHub") {
    var response = await githubClient.Complete(...);
}
// ... more providers, more complexity
Enter fullscreen mode Exit fullscreen mode

This approach leads to:

  • ❌ Hard-coded provider logic scattered everywhere
  • ❌ Difficult to test and maintain
  • ❌ Impossible to switch providers at runtime
  • ❌ Code duplication for similar operations

πŸ’‘ The Solution

Enter Microsoft.Extensions.AI - Microsoft's official abstraction layer for AI services:

// βœ… The solution: One interface for all providers
IChatClient client = GetClient(provider);
var response = await client.CompleteAsync(messages, options);
Enter fullscreen mode Exit fullscreen mode

By using the Provider Pattern with a Factory, we can:

  • βœ… Switch providers with a configuration change
  • βœ… Add new providers without touching existing code
  • βœ… Test with mock providers easily
  • βœ… Keep code clean and maintainable

πŸ—οΈ Architecture Overview

Let me show you the architecture we'll build:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Console Application (Interactive Menus)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SimpleChat.cs (Main Orchestrator)                  β”‚
β”‚  β€’ Provider Selection                               β”‚
β”‚  β€’ Chat Options Configuration                       β”‚
β”‚  β€’ Chat Loop                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                β”‚                β”‚
    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
    β”‚ Config  β”‚      β”‚ Factory β”‚     β”‚IChatClientβ”‚
    β”‚ Manager β”‚      β”‚ Pattern β”‚     β”‚(MS Ext AI)β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚             β”‚             β”‚         β”‚
        β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”     β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”    β”Œβ”€β”€β”€β–Όβ”€β”€β”€β” β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”
        β”‚GitHub β”‚     β”‚Azure  β”‚    β”‚OpenAI β”‚ β”‚Local  β”‚
        β”‚Models β”‚     β”‚OpenAI β”‚    β”‚  API  β”‚ β”‚Models β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

πŸ“š Design Patterns in Action

1. Provider Pattern (Strategy Pattern)

The Provider Pattern allows us to define a family of algorithms (providers) and make them interchangeable:

// All providers implement this interface
public interface IModelProvider
{
    string ProviderName { get; }
    string ModelName { get; }
    IChatClient CreateChatClient();
}

// Each provider has its own implementation
public class GitHubModelProvider : IModelProvider
{
    public string ProviderName => "GitHub Models";
    public string ModelName => _settings.ModelName;

    public IChatClient CreateChatClient()
    {
        var credential = new AzureKeyCredential(_token);
        return new ChatCompletionsClient(
            new Uri(_settings.EndpointUrl), 
            credential
        ).AsIChatClient(_settings.ModelName);
    }
}
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • Adding a new provider? Just implement IModelProvider
  • No changes to existing code
  • Easy to test with mock implementations

2. Factory Pattern

The Factory Pattern centralizes object creation:

public static class ModelProviderFactory
{
    public static IModelProvider CreateProvider(
        string providerType,
        ProviderSettings settings,
        IConfiguration configuration)
    {
        var token = GetTokenIfNeeded(settings.TokenConfigKey, configuration);

        return providerType.ToLower() switch
        {
            "github" => new GitHubModelProvider(settings, token),
            "azure" => new AzureOpenAIProvider(settings, token),
            "openai" => new OpenAIProvider(settings, token),
            "local" => new LocalModelProvider(settings),
            _ => throw new NotSupportedException(
                $"Provider '{providerType}' not supported")
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • One place to manage provider creation
  • Easy to add new providers (just add a case)
  • Encapsulates complex initialization logic

πŸš€ Building the Application - Step by Step

Let's build this application from scratch!

Step 1: Create the Project

dotnet new console -n AIChat -f net10.0
cd AIChat
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Required Packages

dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.AzureAIInference
dotnet add package Microsoft.Extensions.AI.OpenAI
dotnet add package Azure.AI.Inference
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
Enter fullscreen mode Exit fullscreen mode

Step 3: Define the Provider Interface

Create Providers/IModelProvider.cs:

using Microsoft.Extensions.AI;

namespace AIChat.Providers
{
    public interface IModelProvider
    {
        string ProviderName { get; }
        string ModelName { get; }
        IChatClient CreateChatClient();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Implement GitHub Models Provider

Create Providers/GitHubModelProvider.cs:

using Azure;
using Azure.AI.Inference;
using Microsoft.Extensions.AI;

namespace AIChat.Providers
{
    public class GitHubModelProvider : IModelProvider
    {
        private readonly ProviderSettings _settings;
        private readonly string _token;

        public string ProviderName => "GitHub Models";
        public string ModelName => _settings.ModelName;

        public GitHubModelProvider(ProviderSettings settings, string token)
        {
            _settings = settings;
            _token = token;
        }

        public IChatClient CreateChatClient()
        {
            var credential = new AzureKeyCredential(_token);

            return new ChatCompletionsClient(
                new Uri(_settings.EndpointUrl), 
                credential
            ).AsIChatClient(_settings.ModelName);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Create Configuration Classes

Create AppSettings.cs:

namespace AIChat
{
    public class AppSettings
    {
        public string SelectedProvider { get; set; } = "GitHub";
        public Dictionary<string, ProviderSettings> Providers { get; set; } = new();
        public ChatOptionsSettings ChatOptions { get; set; } = new();
        public UiSettings UI { get; set; } = new();
    }

    public class ProviderSettings
    {
        public string EndpointUrl { get; set; } = string.Empty;
        public string ModelName { get; set; } = string.Empty;
        public string TokenConfigKey { get; set; } = string.Empty;
        public string DeploymentName { get; set; } = string.Empty;
    }

    public class ChatOptionsSettings
    {
        public int MaxOutputTokens { get; set; }
        public float Temperature { get; set; }
    }

    public class UiSettings
    {
        public string WelcomeTitle { get; set; } = string.Empty;
        public string ExitMessage { get; set; } = string.Empty;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Create Configuration File

Create appsettings.json:

{
  "SelectedProvider": "GitHub",
  "Providers": {
    "GitHub": {
      "EndpointUrl": "https://models.github.ai/inference",
      "ModelName": "mistral-ai/Ministral-3B",
      "TokenConfigKey": "GitHub:Token"
    },
    "Azure": {
      "EndpointUrl": "https://your-resource.openai.azure.com",
      "ModelName": "gpt-4",
      "DeploymentName": "gpt-4",
      "TokenConfigKey": "Azure:ApiKey"
    },
    "OpenAI": {
      "ModelName": "gpt-4",
      "TokenConfigKey": "OpenAI:ApiKey"
    },
    "Local": {
      "EndpointUrl": "http://localhost:11434",
      "ModelName": "llama2",
      "TokenConfigKey": ""
    }
  },
  "ChatOptions": {
    "MaxOutputTokens": 300,
    "Temperature": 0.2
  },
  "UI": {
    "WelcomeTitle": "Welcome to AI Chat Assistant",
    "ExitMessage": "Goodbye! πŸ‘‹"
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to set it to copy to output directory in your .csproj:

<ItemGroup>
  <None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Step 7: Build the Factory

Create Providers/ModelProviderFactory.cs:

using Microsoft.Extensions.Configuration;

namespace AIChat.Providers
{
    public class ModelProviderFactory
    {
        public static IModelProvider CreateProvider(
            string providerType,
            ProviderSettings settings,
            IConfiguration configuration)
        {
            // Get token from user secrets if needed
            var token = string.Empty;
            if (!string.IsNullOrEmpty(settings.TokenConfigKey))
            {
                token = configuration[settings.TokenConfigKey];
                if (string.IsNullOrEmpty(token))
                {
                    throw new InvalidOperationException(
                        $"Token not found for provider '{providerType}'. " +
                        $"Please set '{settings.TokenConfigKey}' in user secrets.");
                }
            }

            return providerType.ToLower() switch
            {
                "github" => new GitHubModelProvider(settings, token),
                "azure" => new AzureOpenAIProvider(settings, token),
                "openai" => new OpenAIProvider(settings, token),
                "local" => new LocalModelProvider(settings),
                _ => throw new NotSupportedException(
                    $"Provider '{providerType}' is not supported.")
            };
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 8: Create the Main Application

Create SimpleChat.cs:

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using AIChat.Providers;

namespace AIChat
{
    internal class SimpleChat
    {
        public static async Task RunAsync()
        {
            // Load configuration
            var configuration = BuildConfiguration();
            var settings = LoadSettings(configuration);

            // Display provider selection menu
            var selectedProvider = DisplayProviderSelectionMenu(settings);

            // Get provider settings
            if (!settings.Providers.TryGetValue(
                selectedProvider, out var providerSettings))
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(
                    $"❌ Error: Provider '{selectedProvider}' not found.");
                Console.ResetColor();
                return;
            }

            // Create provider
            IModelProvider provider;
            try
            {
                provider = ModelProviderFactory.CreateProvider(
                    selectedProvider, 
                    providerSettings, 
                    configuration);
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"❌ Error: {ex.Message}");
                Console.ResetColor();
                return;
            }

            // Get chat client
            IChatClient client = provider.CreateChatClient();

            var chatOptions = new ChatOptions
            {
                MaxOutputTokens = settings.ChatOptions.MaxOutputTokens,
                Temperature = settings.ChatOptions.Temperature
            };

            // Display welcome
            Console.Clear();
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine($"β•‘  {settings.UI.WelcomeTitle.PadRight(38)}β•‘");
            Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•");
            Console.ResetColor();
            Console.WriteLine($"\nProvider: {provider.ProviderName}");
            Console.WriteLine($"Model: {provider.ModelName}\n");

            // Chat loop
            while (true)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("You: ");
                Console.ResetColor();
                var userQuestion = Console.ReadLine();

                if (string.IsNullOrWhiteSpace(userQuestion) ||
                    userQuestion.Equals("exit", StringComparison.OrdinalIgnoreCase))
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine($"\n{settings.UI.ExitMessage}");
                    Console.ResetColor();
                    break;
                }

                try
                {
                    var messages = BuildMessages(userQuestion);

                    Console.ForegroundColor = ConsoleColor.Blue;
                    Console.Write("\nAssistant: ");
                    Console.ResetColor();

                    var response = await client.CompleteAsync(messages, chatOptions);
                    Console.WriteLine(response.Message.Text);
                    Console.WriteLine();
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine($"\n❌ Error: {ex.Message}");
                    Console.ResetColor();
                    Console.WriteLine();
                }
            }
        }

        private static IConfiguration BuildConfiguration() =>
            new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false)
                .AddUserSecrets<Program>()
                .Build();

        private static AppSettings LoadSettings(IConfiguration configuration)
        {
            var settings = new AppSettings();
            configuration.Bind(settings);
            return settings;
        }

        private static string DisplayProviderSelectionMenu(AppSettings settings)
        {
            Console.Clear();
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("β•‘     Select AI Model Provider          β•‘");
            Console.WriteLine("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•");
            Console.ResetColor();
            Console.WriteLine();

            var providers = settings.Providers.ToList();

            for (int i = 0; i < providers.Count; i++)
            {
                var provider = providers[i];
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine($"  [{i + 1}] {provider.Key} - {provider.Value.ModelName}");
                Console.ResetColor();
            }

            Console.WriteLine();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.Write($"Enter choice [1-{providers.Count}] (or Enter for default): ");
            Console.ResetColor();

            var input = Console.ReadLine();

            if (string.IsNullOrWhiteSpace(input))
                return settings.SelectedProvider;

            if (int.TryParse(input, out int choice) && 
                choice >= 1 && choice <= providers.Count)
            {
                return providers[choice - 1].Key;
            }

            return settings.SelectedProvider;
        }

        private static IEnumerable<ChatMessage> BuildMessages(string userQuestion) =>
            new List<ChatMessage>
            {
                new(ChatRole.System,
                    "You are a helpful, knowledgeable assistant. " +
                    "Provide clear and concise answers."),
                new(ChatRole.User, userQuestion)
            };
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 9: Update Program.cs

using AIChat;

await SimpleChat.RunAsync();
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Setting Up User Secrets

User Secrets is a .NET feature for storing sensitive data securely during development.

Initialize User Secrets

dotnet user-secrets init
Enter fullscreen mode Exit fullscreen mode

This adds a UserSecretsId to your .csproj:

<PropertyGroup>
  <UserSecretsId>your-unique-id-here</UserSecretsId>
</PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

Store Your GitHub Token

First, get a token from GitHub Settings:

  1. Click "Generate new token (classic)"
  2. No scopes needed for GitHub Models
  3. Copy the token (you won't see it again!)

Then store it:

dotnet user-secrets set "GitHub:Token" "ghp_your_token_here"
Enter fullscreen mode Exit fullscreen mode

Verify:

dotnet user-secrets list
Enter fullscreen mode Exit fullscreen mode

🎨 Understanding Microsoft.Extensions.AI

Microsoft.Extensions.AI provides a unified abstraction layer for AI services. Here are the key concepts:

IChatClient Interface

The main interface for chat operations:

public interface IChatClient
{
    Task<ChatCompletion> CompleteAsync(
        IEnumerable<ChatMessage> messages,
        ChatOptions? options = null,
        CancellationToken cancellationToken = default);
}
Enter fullscreen mode Exit fullscreen mode

ChatMessage

Represents a message in a conversation:

new ChatMessage(ChatRole.System, "You are a helpful assistant")
new ChatMessage(ChatRole.User, "What is AI?")
new ChatMessage(ChatRole.Assistant, "AI stands for...")
Enter fullscreen mode Exit fullscreen mode

Roles explained:

  • System: Instructions to the AI (sets behavior)
  • User: Your questions/prompts
  • Assistant: AI's responses (for conversation history)

ChatOptions

Configuration for the chat request:

new ChatOptions
{
    Temperature = 0.7f,        // 0.0-2.0: creativity level
    MaxOutputTokens = 500,     // Maximum response length
    TopP = 0.9f,              // Nucleus sampling
    FrequencyPenalty = 0.0f,  // Reduce repetition
    PresencePenalty = 0.0f    // Encourage new topics
}
Enter fullscreen mode Exit fullscreen mode

Understanding Temperature

Temperature controls randomness in responses:

0.0 ═══════════ 0.7 ═══════════ 1.5 ═══════════ 2.0
Focused        Balanced      Creative       Chaotic
Enter fullscreen mode Exit fullscreen mode

Examples:

Temperature = 0.0 (Deterministic)

Q: What is 2+2?
A: 4
A: 4  (always same)
A: 4
Enter fullscreen mode Exit fullscreen mode

Temperature = 0.7 (Balanced)

Q: Describe a sunset
A: "The sky fills with warm orange and pink hues..."
A: "Golden rays paint the horizon as day fades..."
A: "Vibrant colors dance across the evening sky..."
Enter fullscreen mode Exit fullscreen mode

Temperature = 1.5 (Very Creative)

Q: Describe a sunset
A: "Celestial fire dances with liquid gold..."
A: "The sun whispers farewell in a symphony of light..."
A: "Time melts into an amber ocean..."
Enter fullscreen mode Exit fullscreen mode

🎯 Adding More Providers

Want to add Azure OpenAI? Here's the complete implementation:

AzureOpenAIProvider.cs

using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.AI;

namespace AIChat.Providers
{
    public class AzureOpenAIProvider : IModelProvider
    {
        private readonly ProviderSettings _settings;
        private readonly string _token;

        public string ProviderName => "Azure OpenAI";
        public string ModelName => _settings.ModelName;

        public AzureOpenAIProvider(ProviderSettings settings, string token)
        {
            _settings = settings;
            _token = token;
        }

        public IChatClient CreateChatClient()
        {
            var credential = new AzureKeyCredential(_token);
            var client = new AzureOpenAIClient(
                new Uri(_settings.EndpointUrl), 
                credential);

            var chatClient = client.GetChatClient(_settings.DeploymentName);
            return chatClient.AsIChatClient();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! No changes to existing code needed. Just:

  1. Add the provider class
  2. Update the factory with a new case
  3. Add configuration to appsettings.json
  4. Set the API key in user secrets

πŸ§ͺ Testing Your Application

Run with GitHub Models

# Set token
dotnet user-secrets set "GitHub:Token" "ghp_your_token"

# Run
dotnet run
Enter fullscreen mode Exit fullscreen mode

Try Different Models

Edit appsettings.json:

"ModelName": "openai/gpt-4o-mini"  // Switch to GPT-4o mini
Enter fullscreen mode Exit fullscreen mode

Available GitHub models:

  • mistral-ai/Ministral-3B - Fast and efficient
  • meta-llama/Llama-3.2-3B-Instruct - Great for instructions
  • microsoft/Phi-3-mini-4k-instruct - Excellent for code
  • openai/gpt-4o-mini - Best quality

Browse all: GitHub Models Marketplace

πŸ’‘ Key Learnings

1. Provider Pattern Makes Code Flexible

By abstracting providers behind an interface:

  • βœ… Easy to add new providers
  • βœ… Easy to test (mock providers)
  • βœ… Easy to maintain (separation of concerns)

2. Factory Pattern Centralizes Creation

Having one place to create providers:

  • βœ… Consistent initialization
  • βœ… Easier to debug
  • βœ… Single source of truth

3. Configuration-Driven Development

Using appsettings.json and User Secrets:

  • βœ… No hardcoded values
  • βœ… Environment-specific configs
  • βœ… Secure secret management

4. Microsoft.Extensions.AI Abstractions

Using official abstractions:

  • βœ… Future-proof code
  • βœ… Community support
  • βœ… Consistent APIs

πŸš€ Enhancing the Application

Want to take this further? Here are some ideas:

1. Add Streaming Responses

await foreach (var update in client.CompleteStreamingAsync(messages, options))
{
    Console.Write(update.Text);
}
Enter fullscreen mode Exit fullscreen mode

2. Add Conversation History

private static List<ChatMessage> _conversationHistory = new();

// Add to history after each exchange
_conversationHistory.Add(new ChatMessage(ChatRole.User, userInput));
_conversationHistory.Add(new ChatMessage(ChatRole.Assistant, response));

// Use history in next request
var allMessages = _conversationHistory.Concat(newMessages);
Enter fullscreen mode Exit fullscreen mode

3. Add Function Calling

var tools = new List<ChatTool>
{
    ChatTool.CreateFunctionTool(
        "get_weather",
        "Get the current weather",
        // ... parameters
    )
};

var options = new ChatOptions { Tools = tools };
Enter fullscreen mode Exit fullscreen mode

4. Add RAG (Retrieval Augmented Generation)

// 1. Search your documents
var relevantDocs = await SearchDocuments(userQuestion);

// 2. Add context to system message
var context = string.Join("\n", relevantDocs);
var systemMessage = new ChatMessage(ChatRole.System,
    $"Use this context to answer questions:\n{context}");
Enter fullscreen mode Exit fullscreen mode

πŸŽ“ What You've Learned

In this tutorial, you've learned:

  1. βœ… Design Patterns - Provider and Factory patterns in action
  2. βœ… Microsoft.Extensions.AI - Official .NET AI abstractions
  3. βœ… Multi-Provider Support - Work with 4 different AI services
  4. βœ… User Secrets - Secure credential management
  5. βœ… Clean Architecture - Separation of concerns
  6. βœ… Configuration Management - appsettings.json best practices

πŸ“Š Performance Considerations

Token Usage

Different providers have different costs:

Provider Input (1K tokens) Output (1K tokens)
GitHub Models FREE FREE
OpenAI GPT-4 $0.03 $0.06
OpenAI GPT-3.5 $0.002 $0.002
Azure Similar to OpenAI Similar to OpenAI
Local (Ollama) FREE FREE

Speed Comparison

From my testing:

Provider Average Response Time
Local Models ⚑ 1-2 seconds
GitHub Models ⚑⚑ 2-3 seconds
OpenAI GPT-3.5 ⚑⚑ 2-4 seconds
OpenAI GPT-4 ⚑⚑⚑ 5-10 seconds
Azure OpenAI ⚑⚑ 2-5 seconds

πŸ”§ Troubleshooting

"Token not found" Error

# Check if secret is set
dotnet user-secrets list

# Set it if missing
dotnet user-secrets set "GitHub:Token" "ghp_your_token"
Enter fullscreen mode Exit fullscreen mode

HTTP 401 (Unauthorized)

  • Verify token format (GitHub: ghp_..., OpenAI: sk-...)
  • Check token hasn't expired
  • Regenerate token if needed

"Provider not found" Error

  • Check SelectedProvider spelling in appsettings.json
  • Ensure provider exists in Providers section
  • Provider names are case-sensitive

🌟 Next Steps

Want to explore more? Check out:

  1. Build a Web UI - Convert to ASP.NET Core
  2. Add Authentication - Secure your application
  3. Deploy to Azure - Host in the cloud
  4. Add Monitoring - Application Insights
  5. Build a Mobile App - .NET MAUI frontend

πŸ“¦ Complete Source Code

The complete source code for this project is available on GitHub:

Repository: Rahul1994jh/genai_with_dotnet

⭐ Please star the repository if you find it helpful!

Clone and run:

git clone https://github.com/Rahul1994jh/genai_with_dotnet.git
cd genai_with_dotnet/genai_with_dotnet/01_SimpleChat
dotnet restore
dotnet user-secrets set "GitHub:Token" "your_token"
dotnet run
Enter fullscreen mode Exit fullscreen mode

🀝 Contributing

Found a bug or want to add a feature? Contributions are welcome!

  1. Fork the repository
  2. Create a feature branch
  3. Submit a pull request

πŸ’¬ Let's Discuss!

What do you think about this approach? Have you used Microsoft.Extensions.AI? What AI provider do you prefer?

Drop a comment below! πŸ‘‡


πŸ“š Resources


If you found this helpful, please:

  • ⭐ Star the GitHub repository
  • πŸ”„ Share this post
  • πŸ’¬ Drop a comment
  • πŸ‘₯ Follow me for more .NET content

Happy coding! πŸš€


This is part of my **Building AI Apps with .NET* series. Stay tuned for more posts on advanced topics like RAG, function calling, and building production-ready AI applications!*

dotnet #csharp #ai #machinelearning #tutorial #programming #softwaredevelopment #technology #github #azure #openai

Top comments (0)