← Code Recipes
Frameworks & SDKs

Microsoft.Extensions.AI Free code examples in C#

The .NET abstraction over every chat provider — DI, middleware and telemetry included.

C# Python Java JavaScript

WHAT THIS PAGE COVERS

Swapping OpenAI for a local model by changing one registration.

Model gpt-5.4-mini
Package Microsoft.Extensions.AI
Checked

Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.

C#
Install

Three packages: the abstractions, the middleware, and one provider. Microsoft.Extensions.AI pulls in the abstractions on its own, so applications reference it and libraries reference only the abstractions.

dotnet add package Microsoft.Extensions.AI
dotnet add package OpenAI
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease

setx OPENAI_API_KEY "sk-your-key"     # Windows, then open a new shell
export OPENAI_API_KEY="sk-your-key"   # macOS / Linux
One interface, one provider

AsIChatClient() is the adapter. From here nothing in your code names OpenAI — the provider is a detail of the line that built the client.

using Microsoft.Extensions.AI;
using OpenAI;

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
    ?? throw new InvalidOperationException("OPENAI_API_KEY is not set.");

IChatClient client = new OpenAIClient(apiKey)
    .GetChatClient("gpt-5.4-mini")
    .AsIChatClient();

List<ChatMessage> history =
[
    new(ChatRole.System, "You are a concise technical assistant."),
    new(ChatRole.User, "Explain a database index in two sentences."),
];

ChatResponse response = await client.GetResponseAsync(history);
Console.WriteLine(response.Text);
Streaming

GetStreamingResponseAsync yields ChatResponseUpdate. Append the text to your history yourself — the SDK does not close the loop for you, and forgetting it makes the model repeat itself.

history.Add(new ChatMessage(ChatRole.User, "Now explain B-trees."));

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

await foreach (ChatResponseUpdate update in
    client.GetStreamingResponseAsync(history))
{
    Console.Write(update.Text);
    assistant.Append(update.Text);
}

// The turn is not finished until it is back in the history.
history.Add(new ChatMessage(ChatRole.Assistant, assistant.ToString()));
The pipeline, which is the actual selling point

UseFunctionInvocation() is the whole tool-calling loop — call, execute, feed back, call again — as one line. The stages compose like ASP.NET Core middleware, and ORDER MATTERS: telemetry outermost, so it measures everything inside it.

using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System.ComponentModel;

[Description("Gets the current stock level for a product code.")]
static int GetStock([Description("The product code")] string code) => 42;

IDistributedCache cache = new MemoryDistributedCache(
    Options.Create(new MemoryDistributedCacheOptions()));

IChatClient pipeline = new ChatClientBuilder(client)
    .UseDistributedCache(cache)      // identical prompts cost nothing
    .UseFunctionInvocation()         // the tool loop, in one line
    .Build();

ChatOptions options = new()
{
    Tools = [AIFunctionFactory.Create(GetStock)],
};

var answer = await pipeline.GetResponseAsync(
    "How many of product X-17 are in stock?", options);

Console.WriteLine(answer.Text);
Registering it properly

In DI the whole pipeline is one registration, and swapping OpenAI for a local model is one line — which is the claim this library is built on and the reason to use it over an SDK directly.

// Program.cs
builder.Services.AddDistributedMemoryCache();

builder.Services
    .AddChatClient(services => new OpenAIClient(
            builder.Configuration["OPENAI_API_KEY"]!)
        .GetChatClient("gpt-5.4-mini")
        .AsIChatClient())
    .UseDistributedCache()
    .UseFunctionInvocation()
    .UseOpenTelemetry(configure: o => o.EnableSensitiveData = false);

// Swapping to a local model is THIS LINE and nothing else:
//   new OpenAIClient(new ApiKeyCredential("ollama"),
//       new OpenAIClientOptions { Endpoint = new Uri("http://localhost:11434/v1") })
//   .GetChatClient("llama3.2").AsIChatClient();

// Anywhere downstream:
app.MapPost("/ask", async (string question, IChatClient chat) =>
    (await chat.GetResponseAsync(question)).Text);

QUESTIONS

Which languages are the Microsoft.Extensions.AI examples in?

C#. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.

Which package do I install for Microsoft.Extensions.AI?

The samples use Microsoft.Extensions.AI. The install step on each language tab gives the exact command.

Which Microsoft.Extensions.AI model do these examples use?

gpt-5.4-mini, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.

Where is the official Microsoft.Extensions.AI documentation?

https://learn.microsoft.com/dotnet/ai/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.

ALSO IN FRAMEWORKS & SDKS

The authority

Official Microsoft.Extensions.AI documentation

Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.

Read the docs ↗

https://learn.microsoft.com/dotnet/ai/

New drops, every week

Free tools & Claude templates, landing regularly.

We keep shipping new free tools, code recipes and Claude Code templates. Follow @freeyantra on Instagram or join the newsletter so you never miss one.

Follow @freeyantra

or get them by email

Double opt-in. Unsubscribe any time. Privacy