L’Architecte .NET face aux LLM : Au-delà du simple appel d'API

L’effervescence autour de l’IA générative a poussé de nombreux projets à intégrer des Large Language Models (LLM) à la hâte. Pour beaucoup, cela se résume à un simple HttpClient pointant vers l’API d’OpenAI.

Pourtant, en architecture logicielle d’entreprise, un LLM n’est qu’une dépendance externe de plus, et sans doute l’une des plus imprévisibles. En tant qu’architectes .NET, notre rôle est de transformer cette “boîte noire” en un composant système fiable, scalable et maintenable.


1. Le Piège du Couplage Fort

Le danger immédiat est de laisser les spécificités d’un fournisseur (OpenAI, Anthropic, Azure AI) infuser dans votre logique métier. Si votre code manipule directement des types propriétaires dans vos services, vous créez une dette technique immédiate.

La solution : L’abstraction via Microsoft.Extensions.AI

Microsoft a standardisé les abstractions pour l’IA. Voici comment découpler votre service de l’implémentation concrète :

// Un service métier qui ne connaît pas le fournisseur final
public class ChatAnalysisService
{
    private readonly IChatClient _chatClient;

    public ChatAnalysisService(IChatClient chatClient)
    {
        _chatClient = chatClient;
    }

    public async Task<string> SummarizeTicketAsync(string ticketDescription)
    {
        var prompt = $"Résume ce ticket de support de manière concise : {ticketDescription}";
        var response = await _chatClient.CompleteAsync(prompt);
        return response.Message.Text;
    }
}

2. RAG et Gestion du Contexte (State Management)

Un LLM est par nature stateless. Injecter tout l’historique ou des documents massifs dans chaque prompt est une erreur coûteuse (le “Prompt Stuffing”). L’architecte doit concevoir une stratégie de RAG (Retrieval-Augmented Generation).

Architecture du flux RAG

  1. Embedding : Transformer la question utilisateur en vecteur numérique.
  2. Retrieval : Chercher les fragments de documents les plus proches dans une base vectorielle.
  3. Augmentation : Injecter ces fragments dans le prompt final.
public async Task<string> AskAiWithContext(string query)
{
    // 1. Vectorisation de la question
    var embedding = await _embeddingGenerator.GenerateAsync(query);

    // 2. Recherche sémantique (ex: Azure AI Search, pgvector, Qdrant)
    var contextDocs = await _vectorStore.GetSimilarDocsAsync(embedding);

    // 3. Construction du prompt enrichi
    var enrichedPrompt = $"""
        Utilise les documents suivants pour répondre.
        Contexte : {string.Join("\n", contextDocs)}

        Question : {query}
        """;

    return await _chatClient.CompleteAsync(enrichedPrompt);
}

3. Gérer la Latence : Streaming et Asynchronisme

Attendre 10 secondes une réponse complète est une mauvaise UX. .NET permet d’implémenter le streaming nativement via IAsyncEnumerable, permettant d’afficher les mots au fur et à mesure de leur génération.

[HttpGet("stream")]
public async IAsyncEnumerable<string> StreamResponse(string prompt)
{
    // Utilisation du streaming natif de Microsoft.Extensions.AI
    var stream = _chatClient.CompleteStreamingAsync(prompt);

    await foreach (var message in stream)
    {
        yield return message.Text ?? string.Empty;
    }
}

4. Observabilité et Résilience (FinOps)

On ne monitore pas un LLM comme une base SQL. L’architecte doit prévoir des mécanismes spécifiques pour garantir la robustesse du système et la maîtrise des budgets :

  • Résilience : Utiliser Polly pour gérer les retries exponentiels sur les erreurs 429 (Too Many Requests) liées aux quotas des API.
  • FinOps : Suivre la consommation de tokens par CorrelationId pour imputer les coûts par service, par client ou par fonctionnalité.
  • Qualité : Logger les “hallucinations” signalées par les utilisateurs via un système de feedback (thumbs up/down) pour affiner le prompt engineering ou enrichir votre base de connaissances.
// Exemple de politique de résilience avec Polly pour les appels LLM
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions {
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true
    })
    .AddTimeout(TimeSpan.FromSeconds(30))
    .Build();

Conclusion : Faire du LLM un choix d’architecture, pas une dépendance subie

L’enjeu pour un architecte .NET n’est pas de savoir appeler un LLM, mais de savoir où, pourquoi et à quelles conditions l’introduire dans le système. Les modèles évolueront, les fournisseurs changeront, les coûts fluctueront — l’architecture, elle, doit rester stable.

Un LLM bien intégré devient :

  • un composant interchangeable, abstrait et gouverné comme n’importe quel service externe,
  • une capacité métier augmentée, enrichie par le contexte applicatif via le RAG,
  • une charge maîtrisée, observable, budgétée et résiliente par design.

À l’inverse, un LLM mal intégré devient rapidement :

  • un point de couplage fort,
  • une source d’imprévisibilité fonctionnelle,
  • un centre de coûts opaque difficile à justifier.

C’est précisément dans cet arbitrage — entre innovation rapide et discipline structurelle — que se situe la valeur de l’architecte. Traiter l’IA générative comme une brique d’infrastructure à part entière n’est pas un frein à l’innovation : c’est ce qui permet de l’industrialiser durablement.

La vraie compétence n’est donc pas “d’ajouter de l’IA”, mais de concevoir un système capable d’en absorber l’évolution sans se renier.