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
| Industry | Chatbot Application |
|---|
| E-Commerce | AI product recommender that understands natural language queries |
| Healthcare | Symptom checker and appointment booking assistant |
| FinTech | Intelligent FAQ bot with context-aware compliance answers |
| DevOps | Internal ChatOps bot that queries logs, CI/CD pipelines, and tickets |
| SaaS | In-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
| Requirement | Details/Notes |
|---|
| .NET SDK 8+ | Recommended: .NET 10. Run dotnet --version to verify. |
| IDE | Visual Studio 2026 v17.8+, VS Code with C# Dev Kit, or JetBrains Rider 2024+ |
| OpenAI API Key | Sign up at platform.openai.com. Free tier gives $5 credit — sufficient for this tutorial. |
| Git | For cloning the companion repo and version-controlling your project. |
| Terminal | PowerShell 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:
| Role | Purpose |
|---|
| system | Sets the AI personality, constraints, and behavioral rules. Sent once at the start. |
| user | Messages typed by the human user. |
| assistant | Previous 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:
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