In module T1 you set up a local model and asked it questions with small file-based apps. Now the Kladde gets its own access to language models. It should do three things: Today it talks to the model on your machine, and later, without a code change, to a model in the cloud. It can be tested without a model. And if it’s set up wrong, you notice at startup and not at a user’s first click.

The tool for this is Microsoft.Extensions.AI, a Microsoft library with one common interface for many providers. In this module you learn its core: the IChatClient in this lesson, streamed responses in the next, structured responses after that, and finally the middleware that measures every call. The examples ran with these packages:

Package Version
Microsoft.Extensions.AI 10.11.0
Microsoft.Extensions.AI.OpenAI 10.11.0
OpenAI 2.15.0
Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Hosting, and in the tests Microsoft.Extensions.Configuration and Microsoft.Extensions.DependencyInjection 10.0.14
Azure.Identity (only for the cloud part) 1.21.0
xunit.v3.mtp-v2 4.0.2

One interface for all models

Every provider of language models has its own library with its own classes. If you program directly against one of them, you rewrite your code as soon as you change providers, and it’s hard to test. So Microsoft.Extensions.AI puts a layer in between. It has three parts:

  • Microsoft.Extensions.AI.Abstractions contains the interfaces and the data types: IChatClient, ChatMessage, ChatRole, ChatOptions, ChatResponse. You write your code against these types.
  • Microsoft.Extensions.AI brings everything that builds on the interface: registration in the container, structured responses, and the middleware for logging, telemetry and caching. It pulls in the abstractions.
  • One package per provider translates between the interface and the provider’s library. For everything that speaks OpenAI’s interface, that’s Microsoft.Extensions.AI.OpenAI. It turns a client from the OpenAI library into an IChatClient with AsIChatClient().

The interface itself is small. It has two methods you ask with and two for housekeeping:

  • GetResponseAsync sends messages and waits for the whole answer.
  • GetStreamingResponseAsync sends the same messages and delivers the answer in pieces as they’re produced (next lesson).
  • GetService asks for services that stand behind the client, for example for its metadata.
  • Dispose releases what the client holds.

Everything else, including GetResponseAsync("a question") with a plain text, is an extension method around these four.