Generative AI  

Build Your First AI Chatbot in C#

Introduction

AI-powered chatbots have fundamentally changed how applications interact with users. From customer support automation to intelligent coding assistants, the ability to embed conversational AI directly into your software stack is now a competitive necessity, not a luxury.

In this guide, you will build a fully functional, production-aware AI chatbot using C# and the OpenAI Chat Completions API. We will go well beyond a basic "Hello, ChatGPT" wrapper — you will implement multi-turn conversation history, streaming responses, structured error handling with retry logic, and a clean console UI with color-coded output.

Why This Matters in 2026

OpenAI processes billions of API calls monthly. Developers who can wire AI capabilities into enterprise .NET applications command premium rates. C# + OpenAI is one of the most searched developer skill combinations on job boards right now. This tutorial will give you a real, extensible chatbot you can evolve into a production service.

Real-World Use Cases

IndustryChatbot Application
E-CommerceAI product recommender that understands natural language queries
HealthcareSymptom checker and appointment booking assistant
FinTechIntelligent FAQ bot with context-aware compliance answers
DevOpsInternal ChatOps bot that queries logs, CI/CD pipelines, and tickets
SaaSIn-app help assistant that reduces support ticket volume by 40-60%

PREREQUISITES

Since this is written for advanced developers, we assume familiarity with C# and .NET. Make sure you have the following ready before writing a single line of code.

Required Tools & Versions

RequirementDetails/Notes
.NET SDK 8+Recommended: .NET 10. Run dotnet --version to verify.
IDEVisual Studio 2026 v17.8+, VS Code with C# Dev Kit, or JetBrains Rider 2024+
OpenAI API KeySign up at platform.openai.com. Free tier gives $5 credit — sufficient for this tutorial.
GitFor cloning the companion repo and version-controlling your project.
TerminalPowerShell 7+, Windows Terminal, or any bash-compatible shell.

OpenAI API Key Setup

Never hardcode API keys. Use environment variables or .NET's user secrets system. Here is the recommended approach for local development:

# .NET User Secrets (production-safe, project-scoped)
dotnet user-secrets init
dotnet user-secrets set "OpenAI:ApiKey" "sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Screenshot 2026-04-24 141025

PROJECT SETUP

Create the .NET Console App

# Create solution and project
dotnet new sln -n AIChatbot
dotnet new console -n AIChatbot.Console
dotnet sln add AIChatbot.Console/AIChatbot.Console.csproj
 
cd AIChatbot.Console

Install NuGet Packages

We use the official OpenAI .NET SDK (v2.x), which provides strongly-typed models, streaming support, and automatic retry logic out of the box.

# Official OpenAI SDK with full Chat Completions support
dotnet add package OpenAI --version 2.1.0
 
# Microsoft Extensions for DI, Configuration, and Logging
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Microsoft.Extensions.Logging.Console
 
# Optional: Polly for advanced retry policies
dotnet add package Polly.Extensions.Http

Project Structure

AIChatbot.Console/
  Models/
    ChatMessage.cs          # Domain model for conversation turns
    ChatSession.cs          # Encapsulates conversation history
  Services/
    IChatService.cs         # Interface for testability
    OpenAIChatService.cs    # Core OpenAI integration
  Helpers/
    ConsoleRenderer.cs      # Color-coded terminal output
  Program.cs               # Entry point + DI wiring
  appsettings.json         # Non-secret configuration

CONNECT TO OPENAI API 

Understanding the Chat Completions API

The Chat Completions endpoint (/v1/chat/completions) is stateless. Every request must include the full conversation history. The API accepts an ordered array of messages with three roles:

RolePurpose
systemSets the AI personality, constraints, and behavioral rules. Sent once at the start.
userMessages typed by the human user.
assistantPrevious responses from the AI model. Required to maintain context across turns.

The ChatMessage Domain Model

using System;
using System.Collections.Generic;
using System.Text;

namespace AIChatbot.Console.Models
{
    public sealed record ChatMessage(string Role, string Content, DateTimeOffset Timestamp = default)
    {
        public static ChatMessage System(string content) => new("system", content, DateTimeOffset.UtcNow);

        public static ChatMessage User(string content) => new("user", content, DateTimeOffset.UtcNow);

        public static ChatMessage Assistant(string content) => new("assistant", content, DateTimeOffset.UtcNow);
    }
}

The ChatSession Model

using System;
using System.Collections.Generic;
using System.Text;

namespace AIChatbot.Console.Models
{
    public sealed class ChatSession
    {
        private readonly List<ChatMessage> _messages = new();
        private readonly int _maxHistoryTurns;

        public ChatSession(string systemPrompt, int maxHistoryTurns = 20)
        {
            _maxHistoryTurns = maxHistoryTurns;
            _messages.Add(ChatMessage.System(systemPrompt));
        }

        public IReadOnlyList<ChatMessage> Messages => _messages.AsReadOnly();

        public void AddUserMessage(string content) =>
            AddAndTrim(ChatMessage.User(content));

        public void AddAssistantMessage(string content) =>
            AddAndTrim(ChatMessage.Assistant(content));

        private void AddAndTrim(ChatMessage message)
        {
            _messages.Add(message);

            while (_messages.Count > 1 + (_maxHistoryTurns * 2))
                _messages.RemoveAt(1);
        }

        public void Reset(string? newSystemPrompt = null)
        {
            var systemContent = newSystemPrompt ?? _messages[0].Content;

            _messages.Clear();
            _messages.Add(ChatMessage.System(systemContent));
        }
    }
}

IChatService Interface in Services

using AIChatbot.Console.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace AIChatbot.Console.Services
{
    public interface IChatService
    {
        Task<string> GetResponseAsync(ChatSession session, CancellationToken ct = default);

        Task StreamResponseAsync(
            ChatSession session,
            Action<string> onToken,
            CancellationToken ct = default);
    }
}

OpenAIChatService Implementation in Services

using AIChatbot.Console.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OpenAI;
using OpenAI.Chat;
using OpenAIChatMessage = OpenAI.Chat.ChatMessage;

namespace AIChatbot.Console.Services;

public sealed class OpenAIChatService : IChatService
{
    private readonly ChatClient _chatClient;
    private readonly ILogger<OpenAIChatService> _logger;

    private const string Model = "gpt-4o-mini";

    public OpenAIChatService(IConfiguration config, ILogger<OpenAIChatService> logger)
    {
        _logger = logger;

        var apiKey = config["OpenAI:ApiKey"]
            ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY")
            ?? throw new InvalidOperationException("OpenAI API key not found.");

        var client = new OpenAIClient(apiKey);
        _chatClient = client.GetChatClient(Model);
    }

    public async Task<string> GetResponseAsync(ChatSession session, CancellationToken ct = default)
    {
        var messages = BuildMessages(session);

        var options = new ChatCompletionOptions
        {
            MaxOutputTokenCount = 1024
        };

        var response = await _chatClient.CompleteChatAsync(messages, options, ct);

        return response.Value.Content[0].Text;
    }

    public async Task StreamResponseAsync(
        ChatSession session,
        Action<string> onToken,
        CancellationToken ct = default)
    {
        var messages = BuildMessages(session);

        var options = new ChatCompletionOptions
        {
            MaxOutputTokenCount = 1024
        };

        await foreach (var update in _chatClient
            .CompleteChatStreamingAsync(messages, options, ct))
        {
            foreach (var part in update.ContentUpdate)
            {
                if (!string.IsNullOrEmpty(part.Text))
                    onToken(part.Text);
            }
        }
    }

    private static List<OpenAIChatMessage> BuildMessages(ChatSession session)
    {
        return session.Messages.Select(m => m.Role switch
        {
            "system" => (OpenAIChatMessage)new SystemChatMessage(m.Content),
            "user" => new UserChatMessage(m.Content),
            "assistant" => new AssistantChatMessage(m.Content),
            _ => throw new ArgumentOutOfRangeException(nameof(m.Role), m.Role, null)
        }).ToList();
    }
}

ConsoleRenderer implementation in Helper

using System;
using System.Collections.Generic;
using System.Text;

namespace AIChatbot.Console.Helpers
{
    public sealed class ConsoleRenderer
    {
        public void PrintWelcome()
        {
            System.Console.Clear();
            System.Console.ForegroundColor = ConsoleColor.Cyan;

            System.Console.WriteLine("╔══════════════════════════════════════╗");
            System.Console.WriteLine("║       Aria — AI Chatbot (C#)         ║");
            System.Console.WriteLine("║  Powered by OpenAI gpt-4o-mini       ║");
            System.Console.WriteLine("╚══════════════════════════════════════╝");

            System.Console.ResetColor();
            System.Console.WriteLine("Commands: /reset /exit Ctrl+C\n");
        }

        public string ReadUserInput()
        {
            System.Console.ForegroundColor = ConsoleColor.Green;
            System.Console.Write("You: ");
            System.Console.ResetColor();

            return System.Console.ReadLine()?.Trim() ?? string.Empty;
        }

        public void BeginAssistantResponse()
        {
            System.Console.ForegroundColor = ConsoleColor.Yellow;
            System.Console.Write("\nAria: ");
            System.Console.ResetColor();
        }

        public void EndAssistantResponse()
        {
            System.Console.WriteLine("\n");
        }

        public void PrintThinking()
        {
            System.Console.ForegroundColor = ConsoleColor.DarkGray;
            System.Console.WriteLine("[Thinking...]");
            System.Console.ResetColor();
        }

        public void PrintError(string message)
        {
            System.Console.ForegroundColor = ConsoleColor.Red;
            System.Console.WriteLine($"ERROR: {message}");
            System.Console.ResetColor();
        }

        public void PrintInfo(string message)
        {
            System.Console.ForegroundColor = ConsoleColor.DarkCyan;
            System.Console.WriteLine(message);
            System.Console.ResetColor();
        }

        public void PrintGoodbye()
        {
            System.Console.ForegroundColor = ConsoleColor.Cyan;
            System.Console.WriteLine("\nGoodbye!");
            System.Console.ResetColor();
        }
    }
}

appsettings.json implementation

{
  "OpenAI": {
    "ApiKey": ""
  }
}

Program.cs implementation

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using AIChatbot.Console.Services;
using AIChatbot.Console.Models;
using AIChatbot.Console.Helpers;

var builder = Host.CreateApplicationBuilder(args);

// ✅ IMPORTANT: Load User Secrets
builder.Configuration.AddUserSecrets<Program>();

// ✅ Register services
builder.Services.AddSingleton<IChatService, OpenAIChatService>();
builder.Services.AddSingleton<ConsoleRenderer>();

using var app = builder.Build();

var chatService = app.Services.GetRequiredService<IChatService>();
var renderer = app.Services.GetRequiredService<ConsoleRenderer>();

var cts = new CancellationTokenSource();

// Ctrl + C handling
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    cts.Cancel();
};

var systemPrompt = """
You are Aria, a C# and .NET expert assistant.
Give clear answers with examples.
""";

var session = new ChatSession(systemPrompt, 10);

renderer.PrintWelcome();

while (!cts.Token.IsCancellationRequested)
{
    var input = renderer.ReadUserInput();

    if (string.IsNullOrWhiteSpace(input))
        continue;

    if (input.Equals("/exit", StringComparison.OrdinalIgnoreCase))
        break;

    if (input.Equals("/reset", StringComparison.OrdinalIgnoreCase))
    {
        session.Reset();
        renderer.PrintInfo("Conversation reset.");
        continue;
    }

    session.AddUserMessage(input);

    renderer.PrintThinking();

    var sb = new System.Text.StringBuilder();

    try
    {
        renderer.BeginAssistantResponse();

        await chatService.StreamResponseAsync(
            session,
            token =>
            {
                Console.Write(token);
                sb.Append(token);
            },
            cts.Token);

        renderer.EndAssistantResponse();

        session.AddAssistantMessage(sb.ToString());
    }
    catch (Exception ex)
    {
        renderer.PrintError(ex.Message);
    }
}

renderer.PrintGoodbye();

In .csproj file

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UserSecretsId>ec4be33e-ebdc-4742-8113-7a1d737cf373</UserSecretsId>
	  <!-- ✅ MUST EXIST -->
	  <UserSecretsId>AIChatbot-Secret-Key</UserSecretsId>
  </PropertyGroup>

Run the Project

dotnet run

How It Works — Conversation Flow

The diagram below shows the complete request/response lifecycle for each user message:

Screenshot 2026-04-24 144000

  BONUS — COMMON ERRORS & FIXES 

Common Errors & Fixes

These are the most frequent issues developers hit when integrating the OpenAI API in .NET, with exact root causes and solutions.

Error 1 — 401 Unauthorized

Screenshot 2026-04-24 144134

Error 2 — 429 Rate Limit / Quota Exceeded

2

Error 3 — Context Length Exceeded

Screenshot 2026-04-24 145925

Error 4 — Streaming Response Appears Garbled

Screenshot_24-4-2026_15319_docs.google.com

Sample Conversation Output

Here is what a multi-turn session looks like in the terminal after running the chatbot:

Screenshot 2026-04-24 150503

GITHUB REPO SECTION 

GitHub Repository

The complete source code for this article, including the full project structure, unit tests, and a Docker-ready version, is available on GitHub:

AIChatbot

How to Clone & Run

git clone https://github.com/yourusername/csharp-openai-chatbot
cd csharp-openai-chatbot/src/AIChatbot.Console
 
# Set your API key
dotnet user-secrets set "OpenAI:ApiKey" "sk-proj-xxxxxxxxxxxx"
 
# Run the chatbot
dotnet run