LLM Chat in .NET con IChatClient: guida completa all’integrazione
Introduzione: l’astrazione che unifica i servizi LLM
Integrare Large Language Model in .NET ha sempre comportato un problema: ogni servizio (OpenAI, Azure OpenAI, Ollama, Claude) ha il proprio SDK con API diverse. IChatClient della libreria Microsoft.Extensions.AI risolve questo problema fornendo un’astrazione unificata. Scrivi una volta, cambia provider senza modificare la logica applicativa.
Cosa è IChatClient?
IChatClient è un’interfaccia che rappresenta un client per servizi AI con capacità chat. Astrae i dettagli di comunicazione con LLM remoti o locali, permettendo di:
- Inviare e ricevere messaggi con contenuto multi-modale (testo, immagini, audio)
- Ottenere risposte complete o streaming incrementale
- Mantenere contesto di conversazione
- Usare funzionalità avanzate come tool calling e structured outputs
L’interfaccia fa parte del pacchetto Microsoft.Extensions.AI.Abstractions, mentre Microsoft.Extensions.AI aggiunge middleware per telemetria, caching, function calling automatico e patterns familiari di dependency injection.
Setup iniziale con DI
Il punto di partenza è registrare il chat client nel contenitore di dependency injection. Ecco l’approccio canonico:
var builder = Host.CreateApplicationBuilder();
builder.Services.AddChatClient(
new OllamaChatClient(new Uri("http://localhost:11434"), "llama3"));
var app = builder.Build();
var chatClient = app.Services.GetRequiredService<IChatClient>();
In questo esempio, usiamo Ollama con il modello llama3 locale. La bellezza di questa astrazione: la stessa registrazione funziona con OpenAI, Azure OpenAI o qualsiasi provider che implementi IChatClient. Il codice che usa il client rimane invariato.
Risposta semplice da un LLM
Il caso più basilare: inviare un prompt e ottenere una risposta:
var response = await chatClient.GetResponseAsync("What is .NET? Reply in 50 words max.");
Console.WriteLine(response.Message.Text);
Il metodo GetResponseAsync restituisce un oggetto ChatCompletion con il messaggio della risposta. Semplice, sincrono dal punto di vista dello sviluppatore (anche se asincrono sottostante).
Streaming per risposte lunghe
Per applicazioni interattive come chatbot, lo streaming è essenziale. Permette all’utente di vedere il testo apparire gradualmente, come in ChatGPT:
var chatResponse = "";
await foreach (var item in chatClient.GetStreamingResponseAsync(chatHistory))
{
Console.Write(item.Text);
chatResponse += item.Text;
}
Il metodo GetStreamingResponseAsync ritorna un IAsyncEnumerable<StreamingChatCompletionUpdate>. Ogni item contiene un frammento di testo che puoi visualizzare in tempo reale.
Conversazioni multi-turno con cronologia
Mantenere una conversazione richiede di raccogliere la storia dei messaggi. Ecco un loop interattivo completo:
var chatHistory = new List<ChatMessage>();
while (true)
{
Console.Write("You: ");
var userPrompt = Console.ReadLine();
chatHistory.Add(new ChatMessage(ChatRole.User, userPrompt));
var chatResponse = "";
Console.Write("Assistant: ");
await foreach (var item in chatClient.GetStreamingResponseAsync(chatHistory))
{
Console.Write(item.Text);
chatResponse += item.Text;
}
Console.WriteLine();
chatHistory.Add(new ChatMessage(ChatRole.Assistant, chatResponse));
}
Ogni turno aggiunge alla lista: il user message, poi il response dell’assistant. Al turno successivo, passi l’intera cronologia a GetStreamingResponseAsync. L’LLM usa questo contesto per mantenere coerenza conversazionale.
Structured output: JSON tipizzato
Spesso vuoi che l’LLM restituisca dati strutturati (JSON). Puoi chiederlo esplicitamente nel prompt:
var prompt = $"""
You will receive an article and extract its metadata.
Respond ONLY with valid JSON following this format without any deviation.
{{
"title": "...",
"summary": "...",
"keywords": ["...", "..."]
}}
Article:
{File.ReadAllText("article.md")}
""";
var response = await chatClient.GetResponseAsync(prompt);
var jsonText = response.Message.Text;
var metadata = JsonSerializer.Deserialize<ArticleMetadata>(jsonText);
L’approccio funziona, ma richiede gestione manuale di parsing e validazione. C’è una soluzione migliore.
Deserialization tipizzata con generics
La libreria Microsoft.Extensions.AI supporta il generic GetResponseAsync<T> che deserializza automaticamente il JSON in una classe C#:
public class ArticleMetadata
{
public string Title { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string[] Keywords { get; set; } = [];
}
var metadata = await chatClient.GetResponseAsync<ArticleMetadata>(prompt);
Console.WriteLine($"Title: {metadata.Result.Title}");
Console.WriteLine($"Keywords: {string.Join(", ", metadata.Result.Keywords)}");
Questa API offre sicurezza in fase di compilazione e supporto IDE completo per il refactoring. Se cambi la struttura di ArticleMetadata, il compilatore avvisa i punti di utilizzo.
Portabilità tra provider: da locale a cloud
Una delle promesse di IChatClient è la portabilità. Ecco come implementare una strategia “local in dev, cloud in prod”:
// Avvio locale con Ollama
if (app.Environment.IsDevelopment())
{
builder.Services.AddChatClient(
new OllamaChatClient(new Uri("http://localhost:11434"), "mistral"));
}
else
{
// Avvio cloud con Azure OpenAI
builder.Services.AddChatClient(
new AzureOpenAIClient(
new Uri(azureEndpoint),
new DefaultAzureCredential()).AsChatClient());
}
Il resto dell’applicazione non cambia. Chiede semplicemente IChatClient al DI container e riceve l’implementazione appropriata. Niente hardcoding, niente API specifiche sparse nel codice.
Middleware per telemetria e caching
Il pacchetto Microsoft.Extensions.AI fornisce middleware composabile. Uno uso comune è aggiungere OpenTelemetry:
var builder = Host.CreateApplicationBuilder();
// Registra OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation());
// Registra il chat client con middleware di telemetria
builder.Services.AddChatClient(baseChatClient)
.UseOpenTelemetry(builder.Services.BuildServiceProvider()
.GetRequiredService<ILoggerFactory>());
Con questo setup, ogni chiamata a IChatClient genera automaticamente span OpenTelemetry tracciabili in strumenti come Application Insights o Jaeger. Nessuna strumentazione manuale necessaria.
Integrazione con il framework Agent
Il framework Agent di Microsoft costruisce sopra IChatClient aggiungendo astrazioni a livello agent: gestione persistente del contesto, tool calling automatico, prompt di sistema, API streaming pulita. Se usi agent, IChatClient rimane il cuore della comunicazione LLM.
Conclusione
IChatClient rappresenta una maturazione nell’integrazione LLM in .NET. Invece di accoppiare il codice a provider specifici, definisci un’astrazione e lascia che l’infrastruttura scelga l’implementazione. Lo streaming, la deserialization tipizzata, la composizione di middleware e la portabilità del provider diventano proprietà di prima classe dell’architettura.
Per qualsiasi team che integra LLM in .NET 2026, IChatClient è il fondamento su cui costruire. Richiede poca configurazione iniziale e ripaga con flessibilità architetturale a lungo termine.
Fonte originale: Microsoft.Extensions.AI libraries – .NET | Microsoft Learn e Working with LLMs in .NET using Microsoft.Extensions.AI
Learn how to use the Microsoft.Extensions.AI libraries to integrate and interact with various AI services in your .NET applications.
learn.microsoft.com
Wolfgang MAEHR
in reply to mhoye • • •tsadilas
in reply to Wolfgang MAEHR • • •mhoye
in reply to mhoye • • •reshared this
Sabrina Web 📎, Aral Balkan, mcc e Oblomov reshared this.
mhoye
in reply to mhoye • • •If this system exists at all, then everyone subject to it is one state-coerced software update from away from their computer working for them only at the whim of that state. Age, gender, race, disability, debt, credit rating, citizenship, neighborhood, search history, political affiliation, all of that plus the state itself is one breach away from no computer working - or only the _right people's_ computers working, you understand - at all.
Age verification is the footgun of public democracy.
reshared this
Aral Balkan, RFanciola e mcc reshared this.
mhoye
in reply to mhoye • • •At the implementation level data is just data, and in a democratic society, human privacy and state sovereignty are the same thing. You wouldn't think so, until you take a hard look into how to implement them, but they are the same thing. And both of them are national security issues.
Nobody will be made safer, by age verification. But everyone will be put at risk by the systems that have to exist to implement it.
can everything be outside now?
in reply to mhoye • • •thank you for this, I've been kind of skeptical of this, and you've moved me significantly closer to your position.
I don't like any state mandated age verification, but I do think we need something along the lines of a consumer opt-in "naive Internet". For all the stuff you talk about that's necessary for daily life we should be able to do that as safely as we can walk down the street.
If people can put up a storefront that leads you into a scam or sex shop on the way to interacting with your local government or doing your homework or paying your utilities that's unacceptable.
Age verification isn't a fix, but ignoring these problems just leaves more space for bad laws and policing.
Irenes (many)
in reply to mhoye • • •The Animal and the Machine
in reply to mhoye • • •@Gargron
And if we have to do this by government law, we should verify to a single gov database that confirms we are who we are to other systems.
All of us giving our identity info to all the systems is stupid squared.
For my next insurer I want the Gov to confirm who I am, not hand over a pile of personal stuff. If the Government can’t do this safely, that’s my proof it shouldn’t be done.
Edit: non-government 3rd party should also be available but the law should state the user gets to chose which, and doesn’t have to use a shitty one forced by a retailer.
itgrrl
in reply to The Animal and the Machine • • •@taatm @Gargron
Advanced Persistent Teapot
in reply to The Animal and the Machine • • •Offbeatmammal
in reply to The Animal and the Machine • • •jimfl
in reply to mhoye • • •If only we had an authentication technology that was shaped vaguely like Macaroons which allowed for opaque and scoped attribute exposure/predicate evaluation
(Of course that’s not the point of the legislation)
lord pthenq1
in reply to mhoye • • •"La verificación de edad constituye un ataque deliberado a la soberanía del sistema, tanto para los individuos como para los países. No existe tal cosa como la «verificación de edad»; lo único que existe es una «verificación de identidad que incluye la edad», y el sistema encargado de realizar dicha verificación no es meramente un sistema de rastreo de usuarios invasivo para la privacidad, sino un interruptor de apagado controlado a distancia, aplicable a cualquier persona de cualquier edad."
@mhoye
Saja From Gaza 🍉
in reply to mhoye • • •Saja From Gaza 🍉
in reply to Saja From Gaza 🍉 • • •Kierkethumbs up convincingly
in reply to mhoye • • •Hey Gus
in reply to mhoye • • •> what about kids that have no / bad parents, doesn’t it make sense then to have a baseline?
Fucking no. We don’t limit everyone’s freedom for an imagined worst case minority. However many heartstrings it tugs on.
Thank you for your description of this too. I’m going to reuse part of it as a way to describe why this is one of the worst possible things that could happen to people who are neutral or supportive.
FUCK
Rob Landley
in reply to mhoye • • •I call it "age discrimination" because it is.
The nominal point is to age discriminate. (The actual point is to prevent anti-ice protests from organizing anonymously on signal next time.)
There's no "verification" in Gavin Newsom's bill, it's attestation like the "are you 18 y/n" clickthroughs sites have had since fosta/sesta under the first trump administration. The point is to mandate something you can then tie to palantir/clear/persona and your platform's TPM/SMM.