Getting Started with Microsoft.Extensions.AI — Part 1: One Interface, Any Model

Hey everyone! Joche here, and today we're kicking off a brand-new series diving into Microsoft.Extensions.AI – a powerful library designed to bring AI capabilities into your .NET applications with elegant, framework-agnostic abstractions. If you've ever dealt with integrating multiple large language models (LLMs) from different providers, you know the headache of managing various SDKs, authentication methods, and API calls. Microsoft.Extensions.AI aims to solve that.
In this first part, we'll tackle one of the library's foundational concepts: the IChatClient abstraction. This single interface lets you interact with any chat model, whether it's OpenAI's GPT-4o in the cloud or a local Ollama model like Phi3, without changing your core application logic.
The IChatClient Abstraction: Your Universal LLM Gateway
The core idea is simple: define a common interface for chat completions, then provide concrete implementations for different providers. This is where IChatClient shines. Take a look at how we declare it in our Program.cs from the 1-ChatClientAbstractions project:
using Microsoft.Extensions.AI;
using OpenAI;
namespace ChatClientAbstractions
{
internal class Program
{
static IChatClient CurrentClient;
static string OpenAiModelId = "gpt-4o";
static string OllamaModelId = " phi3:latest";
// ... rest of the code
}
}
We declare CurrentClient as an IChatClient. This means any code interacting with CurrentClient won't know (or care) if it's talking to OpenAI, Ollama, or any other provider supported by Microsoft.Extensions.AI.
Building Provider-Specific Clients
Now, let's see how we get an actual instance of IChatClient. The library provides extensions to build these clients. Here's how we set up an OpenAI client:
private static IChatClient GetChatClientOpenAiImp(string ApiKey,string ModelId)
{
OpenAIClient openAIClient =new OpenAIClient(ApiKey);
return new OpenAIChatClient(openAIClient, ModelId)
.AsBuilder()
.Build();
}
We first instantiate the underlying OpenAIClient from the official OpenAI SDK, then wrap it with OpenAIChatClient from Microsoft.Extensions.AI, specifying the model ID (gpt-4o in this case). The .AsBuilder().Build() pattern is a common way to configure and finalize AI clients in this library.
What if we want to use a local model, like Phi3 running on Ollama? It's just as straightforward:
private static IChatClient GetChatClientGetOllamaImp(string endpoint, string modelId)
{
return new OllamaChatClient(endpoint, modelId: modelId)
.AsBuilder()
.Build();
}
Here, we use OllamaChatClient, providing the Ollama endpoint and the model ID (phi3:latest). Notice the consistent .AsBuilder().Build() pattern.
Swapping Models with a Single Line
The real magic happens when you want to switch between these providers. In our Main method, you can see how easy it is:
//CurrentClient = GetChatClientOpenAiImp(Environment.GetEnvironmentVariable("OpenAiTestKey"), OpenAiModelId);
CurrentClient=GetChatClientGetOllamaImp("http://127.0.0.1:11434/", OllamaModelId);
By simply commenting out one line and uncommenting another, you can completely swap the underlying LLM provider your application is using! Your application code that uses CurrentClient remains completely untouched. This is the power of a good abstraction.
Making Your First Chat Completion
Once CurrentClient is initialized, making a completion request is trivial. From the 1-ChatClientAbstractions example:
var Prompt = "Describe what is c# in 20 words";
Console.WriteLine(Prompt);
var Result= await CurrentClient.CompleteAsync(Prompt);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(Result.Message);
We define a Prompt and then call await CurrentClient.CompleteAsync(Prompt). The result, Result, contains the Message generated by the model. Again, whether CurrentClient is backed by OpenAI or Ollama, this code snippet doesn't change. It's truly "one interface, any model."
Controlling Completions with ChatOptions
Beyond basic completions, Microsoft.Extensions.AI also gives you fine-grained control over how models generate responses using ChatOptions. Let's look at the 2-ChatCompletions project.
First, a standard completion without any specific options:
var Prompt = "Describe what is C# in 100 words";
Console.WriteLine(Prompt);
ChatCompletion Result = await CurrentClient.CompleteAsync(Prompt);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(Result.Message);
Console.ForegroundColor = ConsoleColor.White;
This will give us a relatively long description of C#. But what if we want to limit the output? We can introduce ChatOptions:
Console.WriteLine(new string('*', 100));
Console.WriteLine("Now we will use the ChatOptions to limit the number of tokens in the output");
var ChatOptions = new ChatOptions()
{
MaxOutputTokens = 20,
};
//Print prompt again "Describe what is C# in 100 words";
Console.WriteLine(Prompt);
Result = await CurrentClient.CompleteAsync(Prompt, ChatOptions);
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(Result.Message);
By creating a ChatOptions instance and setting MaxOutputTokens = 20, we instruct the LLM to provide a much shorter response, regardless of the prompt's implied length. This allows you to control cost, response time, and the verbosity of the AI's output, all through a standardized options object.
Wrapping Up
In this first part, we've laid the groundwork for working with Microsoft.Extensions.AI. You've seen how IChatClient provides a universal interface for interacting with LLMs, how to configure clients for different providers like OpenAI and Ollama, and how to swap them out with minimal code changes. We also touched upon ChatOptions for basic control over completion parameters like MaxOutputTokens.
This abstraction is key to building flexible, future-proof AI applications in .NET. You can find the full demo code for these examples in the egarim/IntroductionToMsAiExtensions repository.
Next time, we'll dive deeper into managing chat history and handling different types of messages, including multimodal content! Stay tuned!