LLM-Powered Q&A App with Azure OpenAI
Build a .NET console app that chats with GPT-4o via Azure OpenAI.
Introduction
30 Second Summary
Every time you ask ChatGPT a question, a Large Language Model is breaking your words into tokens, predicting the most likely next word, and generating a response one piece at a time. Understanding how that process works gives you the power to control it.
In this project, you will build a .NET console app that talks to Azure OpenAI's GPT-4o model. You'll experiment with parameters like temperature and token limits to see firsthand how developers control AI behavior.
What You'll Build
A .NET console application that holds multi-turn conversations with GPT-4o through Azure OpenAI, complete with conversation memory and configurable behavior parameters.
By the end of this project, you'll have:
- A configured Azure OpenAI resource with a deployed GPT-4o model.
- A .NET console app that sends chat completion requests and displays token usage.
- An interactive chat loop with conversation memory that proves how context windows work.
- Experiments with temperature and token limits showing how they change model behavior.
- Secret Mission: Add streaming output so responses appear token-by-token in real time.
Are there any prerequisites?
You'll need an Azure subscription with the ability to create Azure OpenAI resources. Basic C# knowledge is helpful, but the project walks you through every line of code.
Create Your Azure OpenAI Resource and Deploy GPT-4o
To build your AI-powered console app, you first need an Azure OpenAI resource. This resource acts as your gateway to GPT-4o, the large language model that will power your Q&A assistant.
In this step, you'll create the resource in the Azure Portal, deploy the GPT-4o model, and grab the credentials your app needs to connect.
In this step, get ready to:
- Create an Azure OpenAI resource with Standard S0 pricing.
- Deploy the GPT-4o model in Azure OpenAI Studio.
- Copy your API key and endpoint URL for later use.
Create the Azure OpenAI resource
- Open your browser and navigate to https://portal.azure.com.
- Sign in with the Microsoft account linked to your Azure subscription.
- In the top search bar, type Azure OpenAI and select it from the results.
- Click Create.
- Select your Subscription from the dropdown.
- Under Resource group, click Create new and give it a name (e.g. llm-explorer-rg).
- Choose a supported region such as East US.
- Enter a unique name for your resource (e.g. llm-explorer-openai).
- Set the Pricing tier to Standard S0.
- Click Review + Create, then click Create.
- Wait for the deployment to complete. This typically takes 1-2 minutes.
What is Azure OpenAI?
Azure OpenAI Service gives you access to OpenAI's language models (like GPT-4o) through your own Azure subscription. You get the same models that power ChatGPT, but hosted on Microsoft's infrastructure with enterprise security and billing through your Azure account.
Why Standard S0?
Standard S0 is the pay-as-you-go tier for Azure OpenAI. You only pay for the tokens you use. There is no upfront cost, and the resource costs nothing while idle.
Don't see Azure OpenAI in the search results?
Make sure you're signed into a subscription that has Azure OpenAI access enabled. All Azure customers are eligible for standard usage without a separate access request.
If your search returns no results, try searching for OpenAI instead. If the issue persists, check that your subscription is active and in good standing.
Help me troubleshoot why I can't find Azure OpenAI in the Azure Portal search.
Deploy the GPT-4o model
Your Azure OpenAI resource is now created, but it doesn't have a model yet. You need to deploy GPT-4o before your app can send it messages.
- Once deployment completes, click Go to resource.
- In the left sidebar, expand Resource Management and click Model deployments.
- Click Manage Deployments to open Azure OpenAI Studio.
- Click Deploy model, then select Deploy base model.
- Choose gpt-4o from the model list and confirm your selection.
- In the Deployment name field, enter gpt-4o.
- Click Deploy.
What is a model deployment?
Azure OpenAI separates the resource (your billing and access point) from the model deployment (the specific AI model you want to use). One resource can host multiple model deployments. The deployment name is what your code references when making API calls.
Why GPT-4o?
GPT-4o is OpenAI's flagship multimodal model. It provides high-quality responses that make LLM concepts easy to observe. It's widely available across Azure regions and well-documented for learning projects.
You should see your gpt-4o deployment listed with a "Succeeded" status in Azure OpenAI Studio.
Deployment stuck or failed?
If deployment fails, your selected region may not have GPT-4o capacity available. Try a different region such as East US 2, West US, or Sweden Central.
Help me fix a failed GPT-4o deployment in Azure OpenAI Studio.
Copy your credentials
Your console app needs two pieces of information to connect to Azure OpenAI: an API key and an endpoint URL.
- Go back to the Azure Portal tab in your browser.
- Navigate to your Azure OpenAI resource (search for the resource name you chose earlier).
- In the left sidebar, expand Resource Management and click Keys and Endpoint.
- Copy the value of KEY 1 and paste it somewhere safe (like a text file or notes app).
- Copy the Endpoint value and save it alongside your key.
What are these credentials for?
The API key authenticates your app so Azure knows who is making the request. The endpoint URL tells your app where to send those requests. Together, they connect your local code to the GPT-4o model running in the cloud.
Keep these values private. Anyone with your key can make API calls billed to your subscription.
Don't see Keys and Endpoint?
Make sure you're looking at your Azure OpenAI resource (not the resource group). The Keys and Endpoint option appears under Resource Management in the left sidebar of the resource itself.
Help me find my Azure OpenAI keys and endpoint in the Azure Portal.
Your Azure OpenAI resource is live and GPT-4o is ready to receive messages. Next up, you'll create the .NET console app that actually talks to it.
Create the .NET Console App and Install the SDK
Your Azure OpenAI resource is live and your GPT-4o model is deployed. Now you need a local project that can talk to it.
In this step, you'll create a .NET console application and install the official OpenAI SDK. This gives you the foundation to start sending messages to your model in the next step.
In this step, get ready to:
- Create a new .NET 8.0 console project.
- Install the OpenAI NuGet package.
- Verify the project builds successfully.
Create the project
The .NET SDK includes a CLI tool called dotnet that scaffolds new projects from templates. You need version 8.0 or later installed on your machine.
- Open a terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal, and pressing Enter.
- Check your .NET version by running this command:
dotnet --version
✔️ I see version 8.0 or higher
Your .NET SDK is ready. Continue to the next instruction below.
ⓧ I see an older version
You have .NET installed, but you need version 8.0 or later. The older version won't support the project template and packages this project uses.
- Download and install the .NET 8 SDK from the official page:
- https://dotnet.microsoft.com/download/dotnet/8.0
- Choose the installer for your operating system and follow the prompts.
- Close and reopen your terminal after installation, then run dotnet --version again to confirm it shows 8.0 or higher.
ⓧ Command not found
The .NET SDK is not installed on your machine yet. You need it to create and run C# projects.
- Download and install the .NET 8 SDK from the official page:
- https://dotnet.microsoft.com/download/dotnet/8.0
- Choose the installer for your operating system and follow the prompts.
- Close and reopen your terminal after installation, then run dotnet --version to confirm it shows 8.0 or higher.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the console project and move into it by running these commands:
dotnet new console -n LlmExplorer
cd LlmExplorer
What do these commands do?
The dotnet new console command creates a new console application from a built-in template. The -n LlmExplorer flag names the project folder and the project file inside it.
Inside the new folder, you'll find a Program.cs file (your main code file) and a LlmExplorer.csproj file (your project configuration).
- Confirm the files were created by running:
ls
You should see LlmExplorer.csproj and Program.cs listed in the output.
Don't see these files?
Make sure you ran cd LlmExplorer after creating the project. The ls command shows files in your current directory, not the parent.
If you see a "Couldn't find a valid template" error, your .NET SDK version may be too old. Run dotnet --version and confirm it's 8.0 or higher.
help me troubleshoot why my dotnet new console command failed
Install the OpenAI NuGet package
The official OpenAI NuGet package is the .NET SDK for calling OpenAI and Azure OpenAI endpoints. Microsoft recommends using this package directly rather than the older Azure-specific wrapper.
- Install the package by running this command:
dotnet add package OpenAI --version 2.10.0
Why OpenAI instead of Azure.AI.OpenAI?
Microsoft's own documentation recommends using the OpenAI package directly. It works with both OpenAI and Azure OpenAI endpoints by setting a custom endpoint URL.
This gives you access to the latest features and a simpler API surface. You'll point it at your Azure endpoint in the next step.
You should see output confirming the package was added successfully.
Package install failed?
Make sure you're inside the LlmExplorer folder (run pwd to check). The dotnet add package command needs to find a .csproj file in the current directory.
If you see a network error, check your internet connection and try again.
help me fix my dotnet add package error
Verify the setup
- Open your project in Visual Studio Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Visual Studio Code, and pressing Enter.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the LlmExplorer folder, then click Open.
- Confirm the project builds by running this command in your terminal:
dotnet build
You should see a "Build succeeded" message with 0 errors and 0 warnings.
Build failed?
If you see errors mentioning the OpenAI package, run dotnet restore first to re-download any missing dependencies.
If you see errors about the target framework, confirm your .NET SDK version is 8.0 or higher with dotnet --version.
help me fix my dotnet build errors
✔️ Awesome, I've got everything!
Great. Double check you've saved all your files.
ⓧ I'd like to double check the full code
Your LlmExplorer.csproj should look exactly like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenAI" Version="2.10.0" />
</ItemGroup>
</Project>
Your Program.cs still has the default template content. You'll replace it in the next step.
Your project is scaffolded and the SDK is in place. Next up, you'll write your first lines of C# code and send a message to GPT-4o.
Send Your First Message to the LLM
Your project is set up and the SDK is installed. Now it's time to write the code that actually talks to GPT-4o.
In this step, you'll create a connection to your Azure OpenAI resource, send it a question, and see the response printed in your terminal. This is the foundation for everything else you'll build.
In this step, get ready to:
- Connect to Azure OpenAI using the ChatClient.
- Send a system message and user message to GPT-4o.
- Experiment with different system message personalities.
Write the initial code
The ChatClient is the main object you use to communicate with GPT-4o. It needs three things: your deployment name, an API key credential, and the endpoint URL of your Azure OpenAI resource.
- In your IDE, open Program.cs inside the LlmExplorer folder.
- Delete all the existing content (the default Hello, World! template).
- Paste the following code to set up the connection to Azure OpenAI:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
// Configuration - replace with your actual values
string endpoint = "https://your-resource-name.openai.azure.com/openai/v1/";
string apiKey = "your-api-key-here";
string deploymentName = "gpt-4o";
// Create the ChatClient pointing at Azure OpenAI
ChatClient client = new(
model: deploymentName,
credential: new ApiKeyCredential(apiKey),
options: new OpenAIClientOptions()
{
Endpoint = new Uri(endpoint)
}
);
What does this code do?
The three using statements import the OpenAI SDK namespaces your code needs.
- endpoint is the URL of your Azure OpenAI resource. The SDK sends requests here.
- apiKey authenticates your app so Azure knows it's allowed to use the model.
- deploymentName tells the SDK which model deployment to target.
- ChatClient wraps all three into a single object you'll use to send messages and receive completions.
- Replace your-resource-name in the endpoint string with the name of your Azure OpenAI resource (the one you copied in Step 1).
- Replace your-api-key-here with the KEY 1 value you copied from the Azure Portal.
Keep your key safe
For this learning project, pasting the key directly in code is fine. In production apps, you would store it in an environment variable or a secrets manager instead.
Now that the client is ready, you need to give it something to say. Chat models work with a list of messages, where each message has a role (system, user, or assistant).
- Add the following code below the ChatClient block you just pasted:
// Build the message list with a system instruction and a user question
List<ChatMessage> messages = new()
{
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely."),
new UserChatMessage("What is a Large Language Model? Explain in 2 sentences.")
};
// Send the messages and print the model's response
ChatCompletion completion = client.CompleteChat(messages);
Console.WriteLine(completion.Content[0].Text);
What does this code do?
You're building a list of ChatMessage objects that the model reads in order.
- SystemChatMessage sets the AI's personality and behavioral rules. The model treats this as background instructions.
- UserChatMessage is your actual question. This is what the model responds to.
- client.CompleteChat(messages) sends the full list to Azure OpenAI and returns a ChatCompletion containing the model's response.
- completion.Content[0].Text extracts the text of that response so you can print it.
- Save Program.cs.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's what your complete Program.cs should look like:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
string endpoint = "https://your-resource-name.openai.azure.com/openai/v1/";
string apiKey = "your-api-key-here";
string deploymentName = "gpt-4o";
ChatClient client = new(
model: deploymentName,
credential: new ApiKeyCredential(apiKey),
options: new OpenAIClientOptions()
{
Endpoint = new Uri(endpoint)
}
);
List<ChatMessage> messages = new()
{
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely."),
new UserChatMessage("What is a Large Language Model? Explain in 2 sentences.")
};
ChatCompletion completion = client.CompleteChat(messages);
Console.WriteLine(completion.Content[0].Text);
Make sure your-resource-name and your-api-key-here are replaced with your actual values from the Azure Portal.
Run and observe the response
Time to see GPT-4o in action. When you run the app, your code sends the messages to Azure OpenAI, the model generates a response, and you see it printed in the terminal.
- Run your app by executing this command in the terminal:
dotnet run
- You should see a 2-sentence explanation of what a Large Language Model is, printed directly in your terminal.
Seeing an error instead of a response?
- If you see a 401 or authentication error, double check that your API key in Program.cs matches KEY 1 from the Azure Portal exactly.
- If you see a 404 or resource not found error, check that your endpoint URL includes your exact resource name and ends with /openai/v1/.
- If you see a deployment not found error, confirm your deployment is named gpt-4o in Azure OpenAI Studio.
Help me debug my Azure OpenAI connection error
What just happened behind the scenes?
Your app just completed a full round-trip to Azure OpenAI. Here are the key concepts at play:
- Tokens: The model broke your text into tokens (roughly 4 characters each). You're billed per token processed.
- System message: The instruction that sets the AI's behavior and personality. The model reads this first.
- User message: Your actual question or input that the model responds to.
- Completion: The model's generated response. It picks the most likely next token, over and over, until it finishes the answer.
Experiment with the system message
The system message is powerful. It controls how the model behaves, what tone it uses, and what constraints it follows. Changing it completely transforms the response you get back.
- In Program.cs, find this line inside the messages list:
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely."),
- Replace the system message text with a new personality. For example:
new SystemChatMessage("You are a pirate who explains technology using nautical metaphors."),
- Save Program.cs and run dotnet run again.
- Notice how the same question gets a completely different response. The model follows the system message instructions.
Why does this matter?
LLMs don't just answer questions. They follow instructions. The system message is how developers shape AI behavior without retraining the model. Every chatbot, coding assistant, and AI product you've used has a system message behind the scenes.
- Try one more personality (e.g., "You are a strict professor who only gives one-word answers.") and run dotnet run again to see the difference.
- When you're done experimenting, change the system message back to the original:
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely."),
- Save Program.cs.
You've just proven that LLMs follow instructions, not just answer questions. Next up, you'll experiment with temperature and token limits to control how creative and how long the responses are.
Experiment with Temperature and Token Limits
In the previous step, you sent a message to GPT-4o and received a response. But how does the model decide what to say? Every time the model generates a word, it picks from a probability distribution of possible next tokens.
Two key parameters let you control this behavior: temperature (how randomly the model picks tokens) and token limits (a hard cap on response length). In this step, you'll experiment with both to see their effects firsthand.
In this step, get ready to:
- Add a ChatCompletionOptions object to control model behavior.
- Experiment with temperature values to see deterministic vs. creative responses.
- Set a low token limit to observe how responses get hard-capped mid-thought.
Add ChatCompletionOptions with zero temperature
The ChatCompletionOptions class lets you pass parameters alongside your messages to control how the model generates its response.
- In Program.cs, find the line that says ChatCompletion completion = client.CompleteChat(messages);.
- Add the following code directly above that line:
// Chat completion options
ChatCompletionOptions options = new()
{
Temperature = 0.0f,
MaxOutputTokenCount = 100
};
- Now update the CompleteChat call to pass in the options. Find this line:
ChatCompletion completion = client.CompleteChat(messages);
- Replace it with:
ChatCompletion completion = client.CompleteChat(messages, options);
What do these options do?
Temperature = 0.0f tells the model to always pick the most likely next token. This makes responses as deterministic as possible.
MaxOutputTokenCount = 100 sets a hard ceiling of 100 tokens on the response. If the model reaches this limit, it stops generating immediately.
- Save Program.cs.
- Run the app three separate times by running this command each time:
dotnet run
Notice how the three responses are nearly identical. With temperature at zero, the model picks the same "best" token every time, producing consistent output.
Seeing a build error?
Make sure you added the options variable above the CompleteChat call, not below it. Also check that you passed options as the second argument: client.CompleteChat(messages, options).
help me fix my ChatCompletionOptions build error
Crank up the temperature
Now let's see what happens when the model samples more randomly from its probability distribution instead of always picking the top choice.
- In Program.cs, find this line inside your ChatCompletionOptions object:
Temperature = 0.0f,
- Change 0.0f to 1.5f, so the line looks like this:
Temperature = 1.5f,
- Save Program.cs and run the app three separate times:
dotnet run
This time, the three responses should vary significantly. Some might even feel quirky or unexpected.
What is temperature doing here?
At temperature 0, the model always picks the single most probable next token. At temperature 1.5, it spreads its attention across many possible tokens, sometimes picking less-likely ones. This is what makes responses feel more "creative" or unpredictable.
For most real applications, a value around 0.7 hits the sweet spot between consistency and natural variation.
Understand what "token limit" means in practice
You've seen how temperature controls randomness. Now let's see what happens when you restrict how many tokens the model is allowed to generate.
- In Program.cs, find this line inside your ChatCompletionOptions:
MaxOutputTokenCount = 100
- Change 100 to 20, so the line looks like this:
MaxOutputTokenCount = 20
- Also update the user message to ask a bigger question. Find this line:
new UserChatMessage("What is a Large Language Model? Explain in 2 sentences.")
- Change the message text to:
new UserChatMessage("Explain quantum computing")
- Save Program.cs and run the app:
dotnet run
The response gets cut off mid-thought. The model didn't choose to stop there. It literally ran out of tokens and was forced to halt.
Why does this matter?
MaxOutputTokenCount is a hard cap, not a suggestion. When the model hits this limit, it stops generating immediately. It won't even finish its current sentence.
This is why LLM responses sometimes appear to end abruptly in production apps. The developer set a token limit to control costs, and the model hit it before completing its thought.
- Now that you've seen both extremes, set balanced values for general use. Change Temperature to 0.7f and MaxOutputTokenCount to 100.
- Restore the user message back to:
new UserChatMessage("What is a Large Language Model? Explain in 2 sentences.")
- Save Program.cs.
- Run dotnet run one more time to confirm the app works with your balanced settings.
Response still getting cut off?
Make sure you set MaxOutputTokenCount back to 100 (not 20). Also double-check that Temperature is 0.7f with the f suffix.
help me fix my temperature and token limit settings
✔️ Awesome, I've got everything!
Great. Double check you've saved Program.cs with the balanced values (Temperature = 0.7f, MaxOutputTokenCount = 100).
ⓧ I'd like to double check the full code
Here's what your complete Program.cs should look like at this point:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
string endpoint = "https://your-resource-name.openai.azure.com/openai/v1/";
string apiKey = "your-api-key-here";
string deploymentName = "gpt-4o";
ChatClient client = new(
model: deploymentName,
credential: new ApiKeyCredential(apiKey),
options: new OpenAIClientOptions()
{
Endpoint = new Uri(endpoint)
}
);
List<ChatMessage> messages = new()
{
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely."),
new UserChatMessage("What is a Large Language Model? Explain in 2 sentences.")
};
ChatCompletionOptions options = new()
{
Temperature = 0.7f,
MaxOutputTokenCount = 100
};
ChatCompletion completion = client.CompleteChat(messages, options);
Console.WriteLine(completion.Content[0].Text);
You now understand the two main knobs developers use to control LLM output. Next up, you'll turn this single-question script into an interactive chat loop with conversation memory.
Build an Interactive Chat Loop with Memory
You've seen how temperature controls creativity and how token limits cap response length. But right now your app is a one-shot tool. You ask one question, get one answer, and the program exits.
Real AI assistants like ChatGPT maintain a conversation. They remember what you said three messages ago. In this step, you'll build that same capability by giving your app a conversation history that grows with each exchange.
In this step, get ready to:
- Refactor your app to maintain conversation history using a message list.
- Build an interactive chat loop that reads input and displays responses with token usage.
- Test that the model remembers earlier messages in your conversation.
Add conversation history
LLMs have no built-in memory. Every time you call the API, the model starts fresh with zero knowledge of past interactions. The only way to give it "memory" is to send the entire conversation history with every request.
This accumulated list of messages is what fits inside the model's context window. The context window is the maximum amount of text (measured in tokens) the model can process at once.
- In Program.cs, replace the entire file contents with the following code:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
// Configuration - replace with your actual values
string endpoint = "https://your-resource-name.openai.azure.com/openai/v1/";
string apiKey = "your-api-key-here";
string deploymentName = "gpt-4o";
// Create the ChatClient pointing at Azure OpenAI
ChatClient client = new(
model: deploymentName,
credential: new ApiKeyCredential(apiKey),
options: new OpenAIClientOptions()
{
Endpoint = new Uri(endpoint)
}
);
// Conversation history - this IS the model's "memory"
List<ChatMessage> messages = new()
{
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely.")
};
// Chat completion options
ChatCompletionOptions options = new()
{
Temperature = 0.7f,
MaxOutputTokenCount = 800
};
Console.WriteLine("=== Azure OpenAI Chat ===");
Console.WriteLine("Type your message and press Enter. Type 'quit' to exit.");
Console.WriteLine();
What changed from before?
- The messages list now starts with only the system message. There is no hardcoded user question. Your questions will be added dynamically during the chat loop.
- MaxOutputTokenCount is now 800 instead of 100. This gives the model room for fuller responses in a conversation.
- The header lines print a welcome banner so the user knows how to interact with the app.
- Save Program.cs.
- Run dotnet run in your terminal. You should see the three header lines printed, and then the program exits immediately (because there's no loop yet).
Seeing a build error?
- Make sure you replaced the entire file contents. If the old ChatCompletion completion = client.CompleteChat(...) line is still there without the loop code, you'll get errors about undeclared variables.
- Check that your endpoint and API key values are still in place from the previous step.
Why isn't my Program.cs compiling after the refactor?
Build the interactive loop
The setup code initializes the conversation, but right now the program just prints headers and exits. You need a loop that continuously reads user input, sends the full conversation history to the model, and appends both the user's message and the model's response to the history.
- Add the following while loop at the bottom of Program.cs, directly below the Console.WriteLine(); line:
while (true)
{
Console.Write("You: ");
string? userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("quit", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Goodbye!");
break;
}
// Add user message to conversation history
messages.Add(new UserChatMessage(userInput));
// Send the full conversation to the model
ChatCompletion completion = client.CompleteChat(messages, options);
// Get the response text
string response = completion.Content[0].Text;
// Display response with token usage
Console.WriteLine($"\nAssistant: {response}");
Console.WriteLine($" [Tokens used - Input: {completion.Usage.InputTokenCount}, Output: {completion.Usage.OutputTokenCount}, Total: {completion.Usage.TotalTokenCount}]");
Console.WriteLine();
// Add assistant response to history so the model "remembers" it next turn
messages.Add(new AssistantChatMessage(response));
}
What does this code do?
- Console.ReadLine() waits for the user to type a message and press Enter.
- Each user message gets added to the messages list as a UserChatMessage before sending.
- client.CompleteChat(messages, options) sends the entire conversation history to the model every single time. The model reads all previous messages to understand context.
- The response gets added back as an AssistantChatMessage so next turn the model can see what it previously said.
- Token usage is displayed after each response so you can see the cost growing as the conversation gets longer.
- Save Program.cs.
- Run dotnet run in your terminal. You should see the header banner followed by a You: prompt waiting for your input.
Not seeing the You: prompt?
- Make sure the while loop is placed after the Console.WriteLine(); line at the bottom of your file, not inside any other block.
- Check that you don't have a leftover client.CompleteChat(messages, options) call outside the loop from the previous version.
Help me figure out why my chat loop isn't starting correctly.
✔️ Awesome, I've got everything!
Great. Double check you've saved Program.cs before moving on.
ⓧ I'd like to double check the full code
Here's the complete Program.cs at this point:
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
// Configuration - replace with your actual values
string endpoint = "https://your-resource-name.openai.azure.com/openai/v1/";
string apiKey = "your-api-key-here";
string deploymentName = "gpt-4o";
// Create the ChatClient pointing at Azure OpenAI
ChatClient client = new(
model: deploymentName,
credential: new ApiKeyCredential(apiKey),
options: new OpenAIClientOptions()
{
Endpoint = new Uri(endpoint)
}
);
// Conversation history - this IS the model's "memory"
List<ChatMessage> messages = new()
{
new SystemChatMessage("You are a helpful AI assistant. You answer questions clearly and concisely.")
};
// Chat completion options
ChatCompletionOptions options = new()
{
Temperature = 0.7f,
MaxOutputTokenCount = 800
};
Console.WriteLine("=== Azure OpenAI Chat ===");
Console.WriteLine("Type your message and press Enter. Type 'quit' to exit.");
Console.WriteLine();
while (true)
{
Console.Write("You: ");
string? userInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals("quit", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Goodbye!");
break;
}
// Add user message to conversation history
messages.Add(new UserChatMessage(userInput));
// Send the full conversation to the model
ChatCompletion completion = client.CompleteChat(messages, options);
// Get the response text
string response = completion.Content[0].Text;
// Display response with token usage
Console.WriteLine($"\nAssistant: {response}");
Console.WriteLine($" [Tokens used - Input: {completion.Usage.InputTokenCount}, Output: {completion.Usage.OutputTokenCount}, Total: {completion.Usage.TotalTokenCount}]");
Console.WriteLine();
// Add assistant response to history so the model \"remembers\" it next turn
messages.Add(new AssistantChatMessage(response));
}
Test conversation memory
Time to prove that conversation history actually works. You'll have a multi-turn conversation and then ask the model to recall something from earlier.
- Run dotnet run if the app isn't already running.
- At the You: prompt, type My name is Alex (or your own name) and press Enter.
- Ask a follow-up question like What is the capital of France? and press Enter.
- Now type What's my name? and press Enter.
The model should correctly recall your name. Notice the input token count grows with each turn because you're sending more conversation history each time.
Why does it remember?
The model itself has no persistent memory between API calls. What makes it "remember" is that your code sends the full messages list every turn. Your name is still in that list from the first message.
If you removed earlier messages from the list, the model would instantly forget them. This is why long conversations eventually hit the context window limit and older messages need to be trimmed.
- Type quit to exit when you're done experimenting.
Your app now holds real conversations with memory. Next up, you'll take this further in the Secret Mission by making responses stream in real time, token by token.
Secret mission
Add Streaming Output
Right now, your app waits for the entire response to generate before showing anything. Real AI assistants like ChatGPT show text appearing word-by-word as the model thinks. Can you upgrade your app to stream responses in real time?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your Azure OpenAI resource costs nothing while idle. You only pay when you make API calls.
Resources you used:
- Azure OpenAI resource (includes GPT-4o model deployment).
- Local LlmExplorer project folder on your Desktop.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with the Azure OpenAI API.
- Your Azure OpenAI resource stays available and costs nothing while idle. You are only charged per API call.
- Your local LlmExplorer folder remains on your Desktop for future use.
Pause - I'll come back to this later
Free up local resources while keeping your Azure setup intact for later.
- Your Azure OpenAI resource is already "paused" by default. It only charges per API call, so leaving it idle costs nothing.
- If the console app is still running in your terminal, type quit and press Enter to exit it.
- Close the terminal window to free up system memory.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is irreversible.
- Delete the Azure OpenAI resource by opening the Azure Portal.
- Search for Resource groups in the top search bar and select it.
- Click on the resource group that contains your Azure OpenAI resource.
- Click Delete resource group at the top of the page.
- Type the resource group name to confirm, then click Delete.
- This removes both the Azure OpenAI resource and the GPT-4o deployment. Billing stops immediately.
Delete the local project folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/LlmExplorer
Windows
rmdir /s /q %USERPROFILE%\Desktop\LlmExplorer
That's a Wrap!
That's a Wrap!
Nice work! You just built your first LLM-powered application from scratch using .NET and Azure OpenAI.
You've learned how to:
- Created and configured an Azure OpenAI resource with a GPT-4o model deployment ready for API calls.
- Experimented with temperature and token limits to understand how LLMs balance creativity vs. determinism.
- Built an interactive chat loop with conversation memory that demonstrates how context windows give LLMs the illusion of remembering past messages.
- Added streaming output so responses appear token-by-token in real time, just like ChatGPT.
Ready to quiz yourself?