
Learn how to build an AI chatbot in C# with the OpenAI API. Step-by-step .NET 8 tutorial with streaming, history & function calling. Start coding now!
Building an AI chatbot is one of the most in-demand skills for .NET developers in 2026, and the good news is that the C# OpenAI API integration story has never been better. With the official OpenAI .NET SDK, you can go from an empty console project to a fully functional, streaming, context-aware chatbot in under an hour. In this complete tutorial, you'll learn how to build an AI chatbot with C# and the OpenAI API from scratch — including conversation history, streaming responses, function calling, and the production best practices most tutorials skip.
We'll explain not just how each piece works, but why it's designed that way, so you can adapt this foundation to web apps, Blazor, ASP.NET Core APIs, or desktop applications.
What You'll Build and What You Need
By the end of this tutorial you'll have a console-based AI chatbot that:
- Sends user messages to OpenAI's chat models and prints responses
- Streams tokens in real time (like ChatGPT's typing effect)
- Remembers conversation history across turns
- Calls your own C# functions when the model needs live data (function calling / tools)
- Handles errors, rate limits, and token budgets like a production app should
Prerequisites:
- .NET 8 SDK or later (everything here also works on .NET 9)
- An OpenAI API key from
platform.openai.com - Basic C# knowledge — async/await and classes
Step 1: Set Up the Project and OpenAI .NET SDK
Create a new console project and add the official OpenAI NuGet package:
dotnet new console -n CSharpChatbot
cd CSharpChatbot
dotnet add package OpenAI
Why the official SDK? For years, C# developers relied on community libraries. The official OpenAI package is now the recommended path: it's strongly typed, actively maintained, supports streaming and tool calling natively, and tracks API changes quickly. It also plugs into Microsoft.Extensions.AI abstractions if you later want provider-agnostic code.
Store Your API Key Securely
Never hardcode your API key. A leaked key in a GitHub repo will be scraped and abused within minutes — this is the single most common (and most expensive) beginner mistake. Use an environment variable:
// PowerShell (persists for your user account):
// [Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "sk-...", "User")
// macOS/Linux:
// export OPENAI_API_KEY="sk-..."
For ASP.NET Core apps, use User Secrets in development and a secret manager (Azure Key Vault, AWS Secrets Manager) in production.
Step 2: Your First Chat Completion in C#
Here's the minimal working chatbot — a complete, runnable Program.cs:
using OpenAI.Chat;
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("Set the OPENAI_API_KEY environment variable.");
ChatClient client = new(model: "gpt-4o-mini", apiKey: apiKey);
ChatCompletion completion = await client.CompleteChatAsync(
"Explain async/await in C# in two sentences.");
Console.WriteLine(completion.Content[0].Text);
Run it with dotnet run and you'll get a response in a couple of seconds.
Why gpt-4o-mini? Model choice is a cost/quality trade-off. Mini-class models are fast and cheap — ideal for development, prototyping, and most chatbot workloads. Swap the model string to a larger model (like gpt-4o or newer flagship models) only when you measurably need the extra reasoning quality. Because the model is just a constructor parameter, upgrading later is a one-line change.
Step 3: Add Conversation History (Memory)
Here's the part beginners often miss: the OpenAI API is stateless. The model has no memory of your previous requests. Every call must include the full conversation so far. "Chat memory" is something you implement by maintaining a list of messages:
using OpenAI.Chat;
string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("Set the OPENAI_API_KEY environment variable.");
ChatClient client = new(model: "gpt-4o-mini", apiKey: apiKey);
List<ChatMessage> messages =
[
new SystemChatMessage(
"""
You are a helpful assistant for C# developers.
Answer concisely and include code examples when useful.
""")
];
Console.WriteLine("C# Chatbot ready. Type 'exit' to quit.\n");
while (true)
{
Console.Write("You: ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
break;
messages.Add(new UserChatMessage(input));
ChatCompletion completion = await client.CompleteChatAsync(messages);
string reply = completion.Content[0].Text;
messages.Add(new AssistantChatMessage(reply));
Console.WriteLine($"\nBot: {reply}\n");
}
Three message roles matter here, and understanding why they exist makes you a better chatbot developer:
- System — sets the bot's persona, rules, and constraints. The model weights this heavily. This is where prompt engineering lives.
- User — what the human typed.
- Assistant — the model's previous replies. Feeding these back is what creates the illusion of memory.
Pitfall: unbounded history. Every message you append costs input tokens on every subsequent request, and models have a finite context window. A long-running chat will get slower, more expensive, and eventually fail. Production chatbots trim old messages, keep a sliding window of the last N turns, or summarize older history into a single system note. Even a simple if (messages.Count > 40) messages.RemoveRange(1, 2); (preserving the system message at index 0) prevents runaway costs.
Step 4: Stream Responses Like ChatGPT
Waiting several seconds for a full response feels broken to users. Streaming prints tokens as they're generated, which dramatically improves perceived performance — the same reason ChatGPT types its answers. The C# OpenAI API SDK makes this an await foreach:
Console.Write("\nBot: ");
AsyncCollectionResult<StreamingChatCompletionUpdate> stream =
client.CompleteChatStreamingAsync(messages);
var fullReply = new System.Text.StringBuilder();
await foreach (StreamingChatCompletionUpdate update in stream)
{
foreach (ChatMessageContentPart part in update.ContentUpdate)
{
Console.Write(part.Text);
fullReply.Append(part.Text);
}
}
Console.WriteLine("\n");
messages.Add(new AssistantChatMessage(fullReply.ToString()));
Note that we still accumulate the complete reply in a StringBuilder — the conversation history needs the full assistant message, not fragments. In an ASP.NET Core API, you'd forward these chunks to the browser via Server-Sent Events or SignalR.
Step 5: Function Calling — Let the Chatbot Use Your C# Code
A chatbot that can only talk is limited: it can't check your database, look up an order, or fetch live weather. Function calling (also called tools) solves this. You describe your C# functions to the model; when a user's question needs one, the model responds with "call this function with these arguments" instead of text. Your code runs the function, returns the result, and the model composes a natural-language answer from it.
using System.Text.Json;
using OpenAI.Chat;
// 1. Describe the tool to the model
ChatTool getOrderStatusTool = ChatTool.CreateFunctionTool(
functionName: "get_order_status",
functionDescription: "Gets the shipping status of a customer order by order ID.",
functionParameters: BinaryData.FromString("""
{
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "The order ID, e.g. ORD-1042" }
},
"required": ["orderId"]
}
"""));
ChatCompletionOptions options = new() { Tools = { getOrderStatusTool } };
// 2. Your actual business logic — the model never sees this code
static string GetOrderStatus(string orderId) =>
orderId == "ORD-1042" ? "Shipped — arriving Thursday" : "Order not found";
// 3. The tool-calling loop
messages.Add(new UserChatMessage("Where is my order ORD-1042?"));
ChatCompletion completion = await client.CompleteChatAsync(messages, options);
while (completion.FinishReason == ChatFinishReason.ToolCalls)
{
messages.Add(new AssistantChatMessage(completion));
foreach (ChatToolCall toolCall in completion.ToolCalls)
{
using JsonDocument args = JsonDocument.Parse(toolCall.FunctionArguments);
string orderId = args.RootElement.GetProperty("orderId").GetString()!;
string result = GetOrderStatus(orderId);
messages.Add(new ToolChatMessage(toolCall.Id, result));
}
completion = await client.CompleteChatAsync(messages, options);
}
Console.WriteLine(completion.Content[0].Text);
// "Your order ORD-1042 has shipped and should arrive Thursday."
Why a loop? The model may need multiple tool calls (or chained calls) before it can answer, so you keep executing tools and re-asking until the finish reason is a normal text response. This loop is the core pattern behind every AI agent framework — once you understand it, libraries like Semantic Kernel and Microsoft.Extensions.AI are much less magical.
Best Practices for a Production C# AI Chatbot
1. Handle Rate Limits and Transient Errors
The API will occasionally return 429 (rate limit) or 5xx errors. The SDK retries some failures automatically, but wrap calls defensively and consider Polly for exponential backoff in high-traffic services:
try
{
ChatCompletion completion = await client.CompleteChatAsync(messages);
}
catch (ClientResultException ex) when (ex.Status == 429)
{
Console.WriteLine("Rate limited — retry after a short delay.");
}
2. Control Costs with Token Budgets
Set MaxOutputTokenCount in ChatCompletionOptions to cap response length, trim conversation history aggressively, and log completion.Usage (input/output token counts) so you can see exactly what each conversation costs. Untracked token usage is how hobby projects end up with surprise bills.
3. Guard Against Prompt Injection
If your chatbot processes user-supplied documents or web content, treat that content as untrusted. Never put secrets in the system prompt, validate tool arguments before executing them (the orderId above should be checked against the authenticated user's own orders), and apply authorization in your code — never trust the model to enforce security rules.
4. Keep the ChatClient Long-Lived
Like HttpClient, create one ChatClient and reuse it. In ASP.NET Core, register it as a singleton in dependency injection rather than constructing it per request.
5. Common Pitfalls Recap
- Hardcoding API keys — use environment variables or a secrets manager.
- Forgetting to append assistant replies to history — the bot "forgets" its own answers.
- Unbounded conversation history — costs and latency grow every turn.
- Blocking on async calls with
.Result— alwaysawait; blocking can deadlock UI and legacy ASP.NET apps. - Ignoring
FinishReason— a response cut off by the token limit (Length) looks like a bug if you don't check why generation stopped.
Where to Go Next
You now have every building block of a real AI product. Natural next steps:
- Web UI: wrap this logic in an ASP.NET Core minimal API and stream to a Blazor or React front end via SignalR.
- RAG (Retrieval-Augmented Generation): embed your documentation with the embeddings API, store vectors in a database, and inject relevant chunks into the prompt so the bot answers from your data.
- Abstractions: adopt
Microsoft.Extensions.AI'sIChatClientso you can swap OpenAI for Azure OpenAI or local models without rewriting your app.
Conclusion: Building an AI Chatbot with the C# OpenAI API
Building an AI chatbot with the C# OpenAI API comes down to five ideas: the API is stateless so you manage history yourself; the system message defines your bot's behavior; streaming makes responses feel instant; function calling connects the model to your real C# code; and production readiness means handling rate limits, token budgets, and security from day one.
Key takeaways:
- The official OpenAI .NET SDK (
dotnet add package OpenAI) is the modern, strongly-typed way to integrate GPT models into C# apps. - Conversation memory is just a
List<ChatMessage>you send with every request — trim it to control cost. - Streaming with
await foreachtransforms user experience for almost no extra code. - The tool-calling loop is the foundation of AI agents — master it here and every framework makes sense.
- Secure your API key, validate tool inputs, and never let the model be your authorization layer.
Copy the code above into a fresh console project, run dotnet run, and you'll have your own AI chatbot in C# running today. From there, the same patterns scale all the way to production-grade .NET AI applications.
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.
Comments
Post a Comment