J’ai longtemps vu les assistants IA comme des chats posés à côté d’un logiciel.

Ils répondaient à des questions.

Ils connaissaient peu le métier.

Ils ne savaient pas toujours où se trouvait l’utilisateur.

Ils pouvaient produire un beau texte. Mais ils restaient à la porte de l’application.

Avec Microsoft Agent Framework, j’ai changé d’approche.

Je n’ai pas ajouté une simple zone de saisie.

J’ai construit une couche agentique commune. Elle comprend l’intention. Elle prépare un plan. Elle choisit une compétence métier. Elle reçoit le contexte de la page. Elle appelle des outils .NET typés. Elle garde une mémoire. Elle peut aussi proposer une action à l’interface.

Cette couche s’appelle SuperAIAgent.

Elle vit dans un dépôt dédié. Elle est distribuée sous forme de package NuGet. Elle s’intègre dans les applications Blazor d’Appliman.

Dans cet article, je vais montrer ce que j’ai réellement construit.

Je vais aussi expliquer pourquoi Microsoft Agent Framework apporte bien plus qu’un appel à un LLM.

Cet article décrit l’architecture réellement mise en œuvre dans SuperAIAgent et dans les logiciels Appliman. Les noms de packages, versions et fournisseurs correspondent au code présenté.

Agents externes et agents intégrés : deux architectures très différentes

Avant de parler du framework, je distingue deux familles d’intégration.

La première repose sur un agent externe.

Je pense par exemple à Hermes, Grok Bot, Codex ou Claude lorsqu’ils travaillent en dehors du logiciel métier.

Ces agents ne vivent pas dans le processus de mon application.

Pour lire un lead, préparer une action ou déclencher un traitement, ils doivent franchir une frontière technique.

Cette frontière peut être :

  • une API REST ou gRPC ;
  • un serveur MCP ;
  • un connecteur ;
  • une interface en ligne de commande ;
  • parfois une automatisation du navigateur.

Je dois alors exposer les capacités nécessaires.

Je dois authentifier l’agent.

Je dois versionner les contrats.

Je dois contrôler les droits à distance.

Je dois aussi gérer les erreurs réseau, la latence et les limites de débit.

Cette architecture est très utile pour un assistant polyvalent.

Elle permet à un même agent de travailler avec plusieurs systèmes.

Mais le métier reste derrière une frontière d’intégration.

La seconde famille repose sur un agent intégré au logiciel.

C’est l’approche que j’ai retenue avec Microsoft Agent Framework dans Appliman.

La couche agentique est enregistrée dans le conteneur d’injection de dépendances .NET.

Elle vit dans le même processus que l’application Blazor Server et ses services métier.

Je peux donc lui fournir directement des fonctions .NET typées.

Ces fonctions deviennent les outils autorisés de l’agent.

Je n’ai pas besoin de publier une nouvelle API simplement pour que l’agent interroge la couche métier.

Je n’ai pas besoin de dupliquer mes règles derrière un contrôleur spécifique.

Je réutilise les services, les handlers, les validations et les autorisations déjà présents dans l’application.

flowchart LR
    subgraph EXT["Agent IA externe"]
        EUSER["Utilisateur"] --> EAGENT["Hermes, Grok Bot, Codex ou Claude"]
        EAGENT --> CONTRACT["API, MCP ou connecteur"]
        CONTRACT --> GATE["Authentification + contrat + réseau"]
        GATE --> EDOMAIN["Métier du logiciel"]
    end

    subgraph INT["Agent intégré avec Microsoft Agent Framework"]
        IUSER["Utilisateur dans Blazor"] --> MAF["Agent et workflow internes"]
        MAF --> TOOLS["Fonctions .NET autorisées"]
        TOOLS --> SERVICES["Services et handlers existants"]
        SERVICES --> IDOMAIN["Métier du logiciel"]
    end
Point de comparaison Agent externe Agent intégré avec MAF
Accès au métier API, MCP ou connecteur Fonctions .NET autorisées
Frontière réseau pour les outils Généralement oui Non, dans le mode local
Contrat Schéma distant à publier et versionner Interfaces et types du code applicatif
Authentification Identité technique dédiée Réutilisation possible du contexte de sécurité applicatif
Usage naturel Assistant transverse à plusieurs systèmes Assistant profondément intégré au métier

Le gain principal est la proximité avec le domaine.

L’agent reçoit seulement les outils que je lui accorde.

Il n’obtient pas un accès libre à la base de données.

Il appelle du code applicatif contrôlé.

Je conserve ainsi le typage, les règles métier et la testabilité.

Cette distinction ne signifie pas que Microsoft Agent Framework fonctionne uniquement en interne.

Le framework sait aussi consommer des API et des serveurs MCP. Il peut également être exposé à distance.

C’est mon choix de déploiement qui rend l’agent interne.

Autre nuance importante : le modèle peut rester hébergé dans le cloud.

Si j’utilise OpenAI, Anthropic ou OpenRouter, une requête part toujours vers le fournisseur du modèle.

L’orchestration et les outils métier restent internes au processus.

Les données transmises au modèle doivent donc rester minimales, autorisées et gouvernées.

Microsoft Agent Framework, c’est quoi exactement ?

Microsoft Agent Framework est le framework unifié de Microsoft pour construire des agents IA et des workflows agentiques en .NET et en Python.

Il reprend deux héritages importants.

Semantic Kernel apportait une base solide pour l’entreprise. Il mettait l’accent sur les connecteurs, la composition, la typage et l’intégration applicative.

AutoGen popularisait des abstractions simples pour faire collaborer des agents.

Microsoft Agent Framework réunit ces deux mondes.

Il propose trois grandes familles de capacités :

  • les agents, pour les tâches ouvertes, conversationnelles et pilotées par un modèle ;
  • les workflows, pour les processus explicites, ordonnés et observables ;
  • le harness, pour les tâches agentiques longues avec planification, mémoire, fichiers et suivi.

Autour de ces blocs, le framework fournit les briques qui manquent souvent dans un prototype :

  • des sessions ;
  • des context providers ;
  • des outils ;
  • des middlewares ;
  • des sorties structurées ;
  • des événements ;
  • des graphes d’exécution ;
  • du checkpointing ;
  • des interactions humaines ;
  • des connecteurs MCP ;
  • des Agent Skills.

La documentation officielle donne une règle que je trouve très saine :

Si une fonction classique suffit, il faut utiliser une fonction classique.

Je partage totalement cette philosophie.

Je ne veux pas remplacer le code métier par un modèle probabiliste.

Je veux utiliser le modèle là où il est bon.

Il comprend le langage naturel.

Il classe une intention.

Il prépare un plan.

Il choisit un outil.

Il reformule une réponse.

Le code .NET, lui, garde les règles, les accès aux données, les validations et les effets de bord.

flowchart LR
    U["Demande en langage naturel"] --> A{"Quelle nature de tâche ?"}
    A -->|"Conversation ouverte"| AG["Agent IA"]
    A -->|"Processus connu"| WF["Workflow typé"]
    A -->|"Règle déterministe"| FN["Fonction .NET"]

    AG --> T["Outils métier contrôlés"]
    WF --> T
    FN --> D["Domaine Appliman"]
    T --> D

    D --> R["Résultat vérifié"]
    R --> UI["Interface Blazor"]

Sa vraie plus-value

La vraie plus-value n’est pas le chat.

Le chat est seulement une interface.

La vraie plus-value est la séparation entre raisonnement, orchestration et exécution métier.

Une autre plus-value est fondamentale.

Microsoft Agent Framework est générique.

Il ne lie pas mes workflows et mon code métier à un fournisseur de modèles précis.

Mes exécuteurs, mes skills et mes fonctions .NET travaillent avec les abstractions du framework.

Ils n’ont pas besoin de savoir si le modèle vient d’OpenAI, d’Anthropic, de Google, d’Azure AI Foundry, d’un modèle local ou d’un autre fournisseur.

Le choix du provider reste isolé dans la couche de création et d’adaptation des clients IA.

Je peux donc changer de modèle ou ajouter un fournisseur sans réécrire le workflow, les règles métier ou l’interface Blazor.

Cette indépendance évite de construire toute l’architecture autour d’un SDK propriétaire.

Elle me permet aussi de choisir le modèle selon le coût, la latence, la confidentialité ou la qualité attendue.

Cette portabilité ne signifie pas que tous les providers sont automatiquement interchangeables.

Chaque intégration doit prendre en charge les fonctions réellement utilisées, notamment les appels d’outils et les sorties structurées.

Je reviens plus loin sur les providers effectivement branchés dans SuperAIAgent.

Un prompt brut mélange souvent tout.

Il mélange l’intention de l’utilisateur, le contexte, les règles de sécurité, les données, les outils et le format de sortie.

Cela devient vite fragile.

Avec un workflow, je peux découper le problème.

Je peux observer chaque étape.

Je peux remplacer une étape.

Je peux ajouter une compétence.

Je peux court-circuiter le LLM quand une reconnaissance déterministe est possible.

Je peux enfin faire évoluer l’assistant comme une vraie brique logicielle.

Mon objectif avec SuperAIAgent

Je voulais un composant commun à l’écosystème Appliman.

Je voulais éviter de réécrire un assistant différent dans chaque logiciel et chaque module métier d’Appliman.

Je voulais aussi éviter une autre dérive.

Je ne voulais pas que chaque écran contienne son propre prompt caché et son propre appel HTTP.

J’ai donc créé une fondation réutilisable.

Le dépôt SuperAIAgent contient aujourd’hui :

  • SuperBlazorAIAgent, le moteur, les workflows, les services et les composants Blazor ;
  • SuperBlazorAIAgent.Abstractions, les contrats partagés ;
  • BlazorDemoApp, une application de démonstration ;
  • SuperBlazorAIAgent.Tests, les tests du moteur.

Le package principal cible .NET 10.

Dans l’état étudié, sa version est 1.2.16.

Il référence notamment :

  • Microsoft.Agents.AI 1.7.0 ;
  • Microsoft.Agents.AI.Workflows 1.7.0 ;
  • Microsoft.Agents.AI.OpenAI 1.7.0 ;
  • Microsoft.Agents.AI.Anthropic 1.0.0-rc3.

Le code consommateur ne dépend pas de tous les détails du framework.

Les contrats essentiels sont isolés.

Cette extraction en abstractions prépare aussi une évolution vers une exécution distante.

Une interface prête à intégrer

La bibliothèque fournit plusieurs composants :

  • AIAgentChat ;
  • AIProviderAndModelSelector ;
  • AIAgentSettings ;
  • AIAgentSettingsEditor ;
  • ApiKeyInput.

Le premier rendu est volontairement simple.

L’utilisateur voit l’état de configuration.

Il choisit un fournisseur.

Il choisit un modèle.

Il peut joindre des fichiers.

Il peut exporter une conversation en PDF.

Le composant de chat SuperAIAgent dans la BlazorDemoApp

La démo locale montre le composant dans son état initial. Aucun fournisseur n’est configuré. L’interface explique clairement l’étape suivante.

Comment j’ai intégré l’agent dans Appliman

Je n’ai pas copié la bibliothèque dans chaque produit.

J’ai choisi un point d’intégration commun.

L’application Appliman.AdminWebApp référence le package SuperBlazorAIAgent.

Elle configure l’agent au démarrage.

Elle ajoute aussi l’assembly de composants Razor au moteur Blazor Server.

Le code de démarrage tient dans une extension claire :

builder.Services.AddAIAgent(options =>
{
    options.AppName = "AdminAppliman";
    options.ConfigurationUrl = "/configuration/agent-ia";
    options.ApiSettings.DefaultProvider = nameof(AIAgentNames.OpenRouter);
    options.ApiSettings.DefaultModel = "auto";
    options.SkillsPath = settings.SkillsPath;
});

Je connecte ensuite la bibliothèque Razor :

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddAdditionalAssemblies(typeof(SuperBlazorAIAgent.StartupExtensions).Assembly);

L’agent apparaît dans le layout global.

Il n’est donc pas limité à une page de démonstration.

Il accompagne l’utilisateur dans l’application d’administration.

Le bouton robot ouvre le panneau Aladin.

Le layout crée un AIAgentUserContext à partir du membre connecté.

J’y place :

  • son identifiant ;
  • son nom ;
  • son adresse email ;
  • ses rôles.

Le composant connaît ainsi l’utilisateur courant.

Il ne traite pas toutes les demandes comme si elles venaient d’un visiteur anonyme.

Le contexte de la page et de l’entité

Une réponse générique est rarement utile dans un logiciel métier.

Quand l’utilisateur consulte un prospect, l’agent doit savoir qu’il consulte ce prospect.

Quand il regarde une liste, l’agent doit connaître la page.

J’ai donc relié le layout aux écrans avec un CascadingValue.

Les pages peuvent appeler SetCurrentEntity.

Le composant transforme ensuite la page et l’entité en AIAgentContextItem sérialisables.

Le moteur les convertit en AIContextProvider avant de créer l’agent.

Ce choix apporte trois bénéfices.

Le contexte est explicite.

Le contrat reste indépendant de l’implémentation interne.

Le transport HTTP futur devient possible.

Les actions suggérées reviennent dans l’interface

Une réponse textuelle ne suffit pas toujours.

Après une recherche, l’agent peut proposer d’ouvrir une fiche.

Après une liste, il peut proposer d’afficher la page correspondante.

J’ai ajouté AIAgentPromptSuggestedAction.

Dans le layout, l’événement SuggestedActionReceived revient vers Blazor.

L’extension de navigation traduit alors l’action métier en URL.

La boucle est complète.

L’utilisateur parle.

L’agent comprend.

Le domaine agit.

L’interface se déplace.

flowchart TB
    subgraph UI["Blazor Server"]
        CHAT["AIAgentChat"]
        PAGE["Page courante"]
        ENTITY["Entité courante"]
        NAV["NavigationManager"]
    end

    subgraph CORE["SuperBlazorAIAgent"]
        CLIENT["IAIAgentChatClient"]
        SERVICE["AIAgentChatService"]
        WORKFLOW["AIAgentPromptWorkflow"]
        FACTORY["AIAgentFactory"]
        SKILLS["Agent Skills"]
        MEMORY["MEMORY.md"]
    end

    subgraph BUSINESS["Capacités métier Appliman"]
        LEAD["Lead management"]
        CONTENT["Préparation de contenus"]
        MESSAGE["Suivi des interactions"]
        CAMPAIGN["Préparation de campagnes"]
    end

    subgraph DATA["Domaine et infrastructures"]
        MEDIATOR["ChannelMediator / handlers"]
        DB["SQL Server"]
        EXTERNAL["APIs et pages publiques"]
    end

    PAGE --> CHAT
    ENTITY --> CHAT
    CHAT --> CLIENT --> SERVICE --> WORKFLOW
    WORKFLOW --> FACTORY
    FACTORY --> SKILLS
    FACTORY --> MEMORY
    WORKFLOW --> LEAD
    WORKFLOW --> CONTENT
    WORKFLOW --> MESSAGE
    WORKFLOW --> CAMPAIGN
    LEAD --> MEDIATOR
    CONTENT --> MEDIATOR
    MESSAGE --> MEDIATOR
    CAMPAIGN --> SERVICE
    MEDIATOR --> DB
    LEAD --> EXTERNAL
    WORKFLOW -->|"SuggestedAction"| CHAT
    CHAT --> NAV

Le cycle complet d’un prompt

Le cœur de SuperAIAgent est AIAgentPromptWorkflow.

Il utilise WorkflowBuilder de Microsoft Agent Framework.

Le workflow principal suit quatre grandes étapes :

  1. analyser l’intention ;
  2. construire un plan ;
  3. choisir le meilleur exécuteur ;
  4. retourner une réponse normalisée.

Chaque message entre et sort avec un type .NET.

Ce détail compte beaucoup.

Je ne fais pas circuler des dictionnaires anonymes entre des boîtes noires.

Je fais circuler des records comme :

  • AIAgentPromptIntentAnalysis ;
  • AIAgentPromptActionPlan ;
  • AIAgentPromptExecutionDecision ;
  • AIAgentWorkflowRunResult.
sequenceDiagram
    autonumber
    actor U as Utilisateur
    participant B as AIAgentChat
    participant C as LocalAIAgentChatClient
    participant S as AIAgentChatService
    participant W as AIAgentPromptWorkflow
    participant I as Intent Analyzer
    participant P as Planner
    participant R as Router
    participant E as Executor métier
    participant F as AIAgentFactory
    participant D as Domaine Appliman

    U->>B: Prompt + fichiers éventuels
    B->>B: Ajoute page, entité et utilisateur
    B->>C: StartAsync(request)
    C-->>B: operationId
    C->>S: SendAsync(request)
    S->>S: Parse et enrichit les fichiers
    S->>W: RunAsync(request)
    W->>I: AIAgentWorkflowRequest
    I->>F: Sortie JSON structurée
    F-->>I: Intention, domaine, risque
    I->>P: AIAgentPromptIntentAnalysis
    P->>F: Sortie JSON structurée
    F-->>P: Plan typé
    P->>R: AIAgentPromptActionPlan
    R->>F: Score des executors
    F-->>R: Décision de routage
    R->>E: AIAgentPromptExecutionDecision
    E->>F: Agent + session + outils
    F-->>E: Instance prête
    E->>D: Appels d’outils typés
    D-->>E: Résultats vérifiés
    E-->>W: AIAgentWorkflowRunResult
    W-->>S: Réponse + tokens + trace
    S-->>C: État terminé
    B->>C: Polling de l’opération
    C-->>B: Résultat final
    B-->>U: Texte + action suggérée

1. L’analyse d’intention

AnalyzePromptIntentExecutor reçoit le prompt.

Il demande une sortie JSON structurée.

Le schéma impose notamment :

  • une intention stable ;
  • un domaine ;
  • un objectif ;
  • les données nécessaires ;
  • les capacités attendues ;
  • un niveau de risque ;
  • un besoin d’approbation ;
  • un score de pertinence ;
  • une justification.

Le niveau de risque peut être Low, Medium ou High.

Une lecture est plutôt basse.

Une recherche externe ou une préparation est moyenne.

Une écriture, une suppression ou un envoi est haute.

Le code force aussi NeedsApproval à vrai quand le risque est haut.

Cette information ne remplace pas une politique d’autorisation.

Elle enrichit la décision.

Elle rend surtout le raisonnement visible et testable.

2. Le planner

PlanPromptExecutor transforme l’intention en étapes atomiques.

Il ne se contente pas de répéter le prompt.

Il décrit le domaine, l’objectif, les étapes, le risque et le besoin d’approbation.

Cela permet au routeur de travailler sur une demande déjà clarifiée.

Je peux aussi afficher cette trace en mode debug.

3. Le routeur

AnalyzePromptExecutor compare le plan aux capacités des exécuteurs enregistrés.

Chaque exécuteur expose :

  • un nom ;
  • une description de ses capacités ;
  • un binding de workflow.

Le routeur produit un score.

Le workflow ajoute une arête conditionnelle vers chaque exécuteur.

Si aucun exécuteur ne convient, UnsupportedPromptExecutor prend la main.

Je préfère un refus clair à une fausse capacité.

4. La réponse

AIAgentPromptResponseExecutor publie la sortie du workflow.

Le service récupère le dernier WorkflowOutputEvent.

Il conserve aussi :

  • les tokens d’entrée ;
  • les tokens de sortie ;
  • la durée ;
  • la trace de debug ;
  • l’action suggérée ;
  • les erreurs éventuelles.

La conservation des échanges passe par IAIAgentPromptHistorySink.

L’implémentation par défaut ne stocke rien.

Une application hôte peut la remplacer.

Le fast path : ne pas payer trois appels IA quand le code sait déjà

C’est une optimisation que j’apprécie particulièrement. ⚡

Certains prompts sont faciles à reconnaître.

Ils contiennent une intention précise.

Ils viennent parfois d’une fonctionnalité déjà connue.

Dans ce cas, lancer l’analyse d’intention, le planner et le routeur serait inutile.

J’ai donc ajouté IAIAgentPromptExecutionMatcher.

Un exécuteur peut reconnaître directement une demande.

Il retourne une décision avec un score de 1.

Le workflow construit alors un graphe court :

flowchart LR
    P["Prompt"] --> M{"Matcher déterministe"}
    M -->|"Reconnu"| E["Executor métier"]
    E --> R["ResponseExecutor"]
    M -->|"Non reconnu"| I["Intent"]
    I --> PL["Planner"]
    PL --> RO["Router"]
    RO --> E2["Executor sélectionné"]
    E2 --> R

La démo ProspectRacingBikeEmailExecutor utilise ce mécanisme.

Elle reconnaît une demande d’email de prospection pour un vélo de course.

Elle prépare un brouillon.

Elle ne l’envoie pas.

Elle renvoie une SuggestedAction.

Le composant Blazor affiche ensuite deux boutons : Envoyer ou Annuler.

Brouillon de prospection avec validation humaine explicite

Le scénario est entièrement local et utilise une adresse réservée en .invalid. L’envoi reste une simulation. La capture montre le principe de human-in-the-loop.

Petite anecdote de conception

Au début, j’aurais pu faire générer puis envoyer le message dans le même appel.

Cela aurait donné une démo spectaculaire.

Cela aurait aussi été une mauvaise expérience produit.

Un agent utile n’est pas un agent qui agit vite.

C’est un agent qui agit au bon niveau d’autonomie.

Le brouillon est automatisé.

La décision d’envoi reste humaine.

Cette frontière est plus importante que le choix du modèle.

La gestion des opérations longues

Une conversation IA peut durer.

Un fichier peut être volumineux.

Un fournisseur peut ralentir.

Une recherche métier peut appeler plusieurs services.

Je ne voulais pas bloquer l’interface Blazor pendant tout ce temps.

LocalAIAgentChatClient crée donc une opération locale.

Il retourne immédiatement un operationId.

Le traitement continue de façon asynchrone.

L’état de l’opération est conservé dans un dictionnaire concurrent.

Les événements passent par un Channel.

Le composant interroge l’état environ toutes les 500 millisecondes.

Il gère trois fins :

  • Completed ;
  • Failed ;
  • Canceled.

L’utilisateur peut annuler.

Le client signale alors le CancellationTokenSource de l’opération.

Cette architecture locale a aussi une autre qualité.

Elle prépare naturellement le passage à un agent distant.

Le projet Abstractions contient déjà les contrats d’opération.

Le dépôt Appliman possède un plan d’implémentation pour séparer le client Blazor du moteur et ajouter un transport HTTP avec polling et SSE.

Je reste précis : dans le code étudié, le client concret est encore local.

Le mode distant est une trajectoire. Pas une fonctionnalité que je prétends déjà terminée.

Les sessions, la mémoire et le contexte

Microsoft Agent Framework fournit la notion de session.

Je l’utilise dans AIAgentFactory.

Une session est mise en cache avec une clé composée de :

  • l’utilisateur ;
  • le fournisseur ;
  • le modèle ;
  • le scope fonctionnel.

Le scope évite de mélanger des conversations qui n’ont rien à voir.

La qualification d’un lead possède son scope.

La préparation d’un contenu commercial possède le sien.

Le suivi des interactions possède aussi son propre contexte.

Je peux enfin isoler une session par action, campagne ou lead lorsque le scénario l’exige.

La mémoire globale

J’ai ajouté un exécuteur Memory.

L’utilisateur peut dire :

Mémorise qu’il ne faut plus afficher les GUID quand ils ne sont pas utiles.

La préférence est écrite dans MEMORY.md à la racine du dossier de skills.

Un verrou asynchrone protège le fichier.

Après une modification, la factory vide le cache des sessions.

Les nouvelles sessions reçoivent donc la mémoire à jour dans leur system prompt.

stateDiagram-v2
    [*] --> SessionAbsente
    SessionAbsente --> SessionActive: création agent + mémoire
    SessionActive --> SessionActive: prompts du même scope
    SessionActive --> MemoireModifiee: "mémorise que..."
    MemoireModifiee --> CacheInvalide: écriture MEMORY.md
    CacheInvalide --> SessionAbsente: ClearAllSessions
    SessionAbsente --> SessionActive: nouvelle session enrichie

Je trouve ce mécanisme très concret.

La mémoire n’est pas cachée dans une base vectorielle opaque.

Elle est lisible.

Elle est versionnable si on le souhaite.

Elle peut être auditée.

Deux formes de skills, deux rôles différents 🧩

Le mot skill peut prêter à confusion.

Dans mon architecture, il existe deux niveaux complémentaires.

Niveau 1 : les exécuteurs métier .NET

Les exécuteurs implémentent IAIAgentPromptExecutor.

Ce sont des capacités compilées.

Elles utilisent les services de l’application.

Elles exposent des outils via AIFunctionFactory.

Elles peuvent lire ou modifier le domaine.

Elles sont enregistrées par injection de dépendances.

Voici une version volontairement générique :

builder.Services.AddTransient<
    IAIAgentPromptExecutor,
    LeadManagementExecutor>();

L’intérêt est architectural.

Le moteur ne connaît pas le détail du lead management.

Il ne connaît pas les écrans, les campagnes ou les règles propres à un produit.

Il connaît seulement le contrat d’extension.

Chaque module apporte ses capacités.

Niveau 2 : les Agent Skills natifs

Microsoft Agent Framework prend aussi en charge le format ouvert Agent Skills.

Un skill est un dossier avec un fichier SKILL.md.

Il peut contenir :

  • des instructions ;
  • des références ;
  • des assets ;
  • des scripts.

Le chargement suit un principe de divulgation progressive.

L’agent connaît d’abord le nom et la description.

Il charge ensuite les instructions détaillées seulement si la demande correspond.

Il lit enfin les ressources utiles à la demande.

Cela évite de gonfler tous les prompts avec toutes les règles.

Dans AIAgentFactory, je crée un AgentSkillsProvider à partir de SkillsPath.

Je l’ajoute à la liste des AIContextProviders.

J’ai aussi pris une décision de sécurité :

var fileOptions = new AgentFileSkillsSourceOptions
{
    AllowedScriptExtensions = []
};

Les scripts sont désactivés.

Les skills fournissent donc du savoir et des procédures.

Ils ne gagnent pas automatiquement le droit d’exécuter du code local.

Mon premier skill natif

Le dépôt contient transactional-email-design.

Ce skill documente la création d’emails transactionnels compatibles avec :

  • Gmail ;
  • Outlook web ;
  • Outlook desktop ;
  • Thunderbird ;
  • Free ;
  • Orange ;
  • Apple Mail.

Il impose des règles très concrètes.

Il recommande les tableaux de présentation.

Il limite la largeur entre 600 et 640 pixels.

Il demande du CSS critique inline.

Il prévoit le preheader.

Il utilise des boutons compatibles Outlook.

Il encadre la syntaxe Handlebars.Net.

La capacité de préparation de contenus demande explicitement à l’agent de charger ce skill lorsqu’une création ou une refonte d’email le nécessite.

Voilà un bon exemple de séparation.

Le SKILL.md contient l’expertise de conception.

L’exécuteur .NET conserve les actions autorisées et le lien avec les services applicatifs.

flowchart TB
    Q["Demande : prépare un email de campagne"] --> ROUTER["Routeur"]
    ROUTER --> EXEC["Capacité métier générique"]
    EXEC --> AGENT["Agent Framework Agent"]

    AGENT --> DISCOVER["Découvre le skill pertinent"]
    DISCOVER --> LOAD["Charge SKILL.md"]
    LOAD --> KNOWLEDGE["Applique les règles de conception"]

    AGENT --> TOOLS["Fonctions .NET autorisées"]
    TOOLS --> SERVICES["Services applicatifs"]
    KNOWLEDGE --> RESULT["Contenu conforme"]
    SERVICES --> RESULT

Une autre anecdote

Le designer d’emails m’a rappelé une chose.

Un modèle peut produire un HTML très moderne.

Il peut aussi produire un email inutilisable dans Outlook desktop.

Le LLM n’a pas besoin de plus de liberté.

Il a besoin de meilleures contraintes.

Le skill transforme une connaissance diffuse en procédure réutilisable.

Je peux l’améliorer sans modifier l’exécuteur.

Je peux aussi le réutiliser avec un autre agent compatible.

Les fonctionnalités couvertes pour le lead management

Je reste volontairement au niveau fonctionnel.

L’objectif n’est pas de documenter chaque outil interne.

L’objectif est de montrer ce que l’agent apporte au cycle de gestion d’un lead.

Les fonctionnalités couvertes sont les suivantes :

  • rechercher et retrouver des leads à partir d’une demande naturelle ;
  • afficher une vue synthétique d’un lead et de son contexte ;
  • créer ou mettre à jour un lead lorsque les informations nécessaires sont présentes ;
  • gérer les contacts associés ;
  • enrichir et qualifier les informations utiles à la prospection ;
  • segmenter les leads selon des critères métier ;
  • préparer un contenu commercial personnalisé ;
  • préparer une campagne ou une prochaine action ;
  • demander les données manquantes avant une opération ;
  • proposer une action de navigation dans l’interface ;
  • conserver la validation humaine avant un envoi ;
  • suivre les interactions et restituer leur état de façon compréhensible.

Ces fonctionnalités restent réparties dans des capacités spécialisées.

L’utilisateur ne voit pas cette découpe technique.

Il formule son besoin dans le chat.

Le workflow identifie l’intention.

Il sélectionne la capacité adaptée.

La capacité appelle ensuite les fonctions .NET qui lui ont été explicitement accordées.

Le domaine conserve le dernier mot.

Une création, une modification ou un envoi n’est confirmé que si le service métier retourne un résultat valide.

Les capacités intégrées au socle

SuperAIAgent enregistre aussi trois capacités génériques :

  • Memory, pour les préférences persistantes ;
  • System, pour les réponses conversationnelles ;
  • CurrentPageOnboarding, pour expliquer la page courante.

Une demande comme « que peux-tu faire ? » emprunte même un raccourci.

Le moteur génère la liste des capacités directement à partir des exécuteurs enregistrés.

Aucun appel IA n’est nécessaire.

Comment cette intégration dessert les logiciels Appliman

Je n’ai pas ajouté une copie du moteur dans chaque binaire.

J’ai intégré l’agent dans le socle commun d’administration et dans les modules métier partagés.

Le même panneau peut donc accompagner les principales étapes du lead management :

  • recherche et consultation ;
  • qualification ;
  • enrichissement ;
  • gestion des contacts ;
  • préparation de contenus ;
  • préparation de campagnes ;
  • validation des actions ;
  • suivi des interactions.

Toutes les fonctions du logiciel n’ont pas besoin de devenir agentiques.

En revanche, chaque module dispose du même chemin d’extension.

Un nouveau module ajoute un IAIAgentPromptExecutor.

Il déclare ses capacités.

Il fournit uniquement les fonctions métier nécessaires.

Le routeur le découvre via l’injection de dépendances.

Le composant Blazor n’a pas besoin d’être modifié.

mindmap
  root((SuperAIAgent))
    Interface commune
      Blazor Server
      Chat intégré
      Contexte page
      Contexte entité
    Socle agentique
      Intention
      Plan
      Routage
      Sessions
      Mémoire
    Lead management
      Recherche
      Qualification
      Contacts
      Contenus
      Campagnes
      Suivi
    Extensions métier
      Nouveaux modules
      Nouvelles fonctions
      Nouveaux skills
    Fournisseurs
      OpenAI
      Anthropic
      OpenRouter

Cette approche apporte de la cohérence.

Le même utilisateur retrouve le même assistant.

Les mêmes conventions de sécurité s’appliquent.

La mémoire reste commune.

Les traces ont le même format.

Chaque métier garde pourtant son propre code.

Le multi-provider sans contaminer le domaine

AIAgentFactory centralise la création des agents.

Au moment du code étudié, trois fournisseurs possèdent un chemin d’exécution concret :

  • OpenAI ;
  • Anthropic ;
  • OpenRouter.

OpenRouter utilise un client compatible avec l’abstraction de chat.

L’application envoie aussi son nom et son URL pour l’attribution.

La configuration visuelle propose les paramètres de modèle, les endpoints locaux et les clés.

Écran de configuration multi-provider de SuperAIAgent

Les paramètres peuvent être conservés dans le stockage protégé du navigateur. La capture ne contient aucune clé.

La factory me permet de changer de fournisseur sans modifier les exécuteurs métier.

Les exécuteurs demandent un agent.

Ils ne construisent pas eux-mêmes un client OpenAI ou Anthropic.

Cette indirection réduit le couplage.

Elle facilite aussi les tests.

Je garde cependant une limite en tête.

L’écran de configuration connaît davantage de familles de fournisseurs que la factory active.

Une option visible ne doit pas être confondue avec un chemin runtime terminé.

L’ajout d’un provider doit donc toujours comprendre :

  • le client ;
  • l’adaptation IChatClient ;
  • les modèles compatibles ;
  • la validation de clé ;
  • les tests de sortie structurée ;
  • les tests d’outils.

Les fichiers et la multimodalité

Le chat accepte des pièces jointes.

Le parseur gère notamment :

  • PDF ;
  • Word ;
  • Excel ;
  • texte ;
  • Markdown ;
  • CSV ;
  • JSON.

Le contenu textuel est injecté dans le prompt avec une limite par fichier.

Les images suivent un chemin multimodal.

La multimodalité traverse toute la chaîne.

Elle concerne le composant Razor, le JavaScript, le stockage temporaire, le parseur et le workflow.

Ce n’est donc pas un simple bouton « joindre » ajouté en façade.

Les garde-fous que j’ai réellement mis en place

Un agent connecté au métier doit être traité comme une surface d’exécution.

Je ne me contente pas d’un avertissement dans le prompt.

J’ai combiné plusieurs protections.

Des outils explicites

L’agent ne reçoit pas un accès générique à la base.

Il reçoit une liste d’outils.

Chaque outil possède :

  • un nom ;
  • une description ;
  • des paramètres typés ;
  • un chemin d’exécution connu.

Le domaine reste souverain

Les écritures passent par les handlers existants.

Les règles métier continuent de s’appliquer.

Le résultat remonte les erreurs et les avertissements.

La confirmation des effets

Les system prompts interdisent de revendiquer une action non confirmée.

Les opérations ambiguës demandent une clarification.

Les envois de campagne restent soumis à une validation humaine.

La démo du brouillon utilise une validation explicite.

Le risque et l’approbation

L’analyseur classe le risque.

Le planner propage le besoin d’approbation.

Je dois néanmoins rester honnête.

Le workflow actuel ne transforme pas encore toutes les opérations à haut risque en pause universelle de type human-in-the-loop.

Chaque exécuteur doit encore appliquer ses propres règles.

La généralisation d’un nœud d’approbation est une amélioration logique.

Les scripts de skills sont désactivés

Le provider de skills reçoit une liste vide d’extensions autorisées.

Un fichier de connaissance ne devient donc pas un exécutable par défaut.

Les secrets

La bibliothèque sait utiliser le stockage protégé de Blazor Server.

Elle ne journalise pas les clés.

Pour un déploiement centralisé, je préfère toutefois un coffre serveur et des identités managées quand le fournisseur le permet.

Le stockage navigateur est pratique pour un mode Bring Your Own Key.

Il déplace aussi la gouvernance vers le poste utilisateur.

Ce choix doit être explicite.

Performance et observabilité

Une architecture agentique peut devenir coûteuse.

J’ai donc cherché les coûts évitables.

Les sessions sont réutilisées

La factory utilise un ConcurrentDictionary.

Elle ne recrée pas une session à chaque message du même scope.

Les fast paths évitent le raisonnement inutile

Un matcher déterministe saute l’intention, le planner et le routeur.

Cela réduit la latence.

Cela réduit aussi les tokens.

Les demandes de capacités sont locales

« Que peux-tu faire ? » ne contacte pas le fournisseur.

La réponse vient des CapabilitiesPrompt.

Les sorties structurées limitent l’ambiguïté

L’intention, le plan et le routage utilisent des schémas JSON.

Le modèle ne renvoie pas un paragraphe qu’il faudrait parser avec des expressions régulières.

Les traces sont natives au workflow

AIAgentWorkflowDebugTrace enregistre les étapes :

  • démarrage ;
  • sélection de chemin ;
  • intention ;
  • plan ;
  • arêtes prises ;
  • score ;
  • exécuteur ;
  • nombre d’événements ;
  • tokens ;
  • longueur de réponse ;
  • erreur fournisseur.

Le mode debug peut afficher la trace dans le chat.

Le service mesure aussi la durée.

La persistance des échanges est injectable.

Les opérations peuvent être annulées

L’utilisateur n’est pas prisonnier d’une requête lente.

Le token d’annulation traverse le workflow, l’agent et les outils asynchrones.

Ajouter une nouvelle capacité métier

Le chemin d’extension est court.

Étape 1 : définir la responsabilité

Je commence par une capacité étroite.

Par exemple :

Qualifier et suivre les leads prioritaires.

Je liste ensuite ce que l’agent peut réellement faire.

Étape 2 : créer l’exécuteur

public sealed class LeadQualificationExecutor
    : Executor<AIAgentPromptExecutionDecision, AIAgentWorkflowRunResult>,
      IAIAgentPromptExecutor
{
    public string Name => "LeadQualification";

    public string CapabilitiesPrompt => """
        Rechercher les leads prioritaires.
        Expliquer leur qualification.
        Préparer la prochaine action.
        """;

    public ExecutorBinding WorkflowBinding => this;

    public override async ValueTask<AIAgentWorkflowRunResult> HandleAsync(
        AIAgentPromptExecutionDecision input,
        IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        // Création de l’agent, outils autorisés et résultat vérifié.
        throw new NotImplementedException();
    }
}

Étape 3 : exposer des outils étroits

Je crée une fonction par opération.

Je décris précisément les paramètres.

Je réutilise les handlers métier.

Je n’expose jamais un outil « exécuter du SQL ».

Étape 4 : enregistrer dans DI

services.AddTransient<
    IAIAgentPromptExecutor,
    LeadQualificationExecutor>();

Le routeur découvre alors la capacité.

Le chat peut la lister.

Le workflow peut la sélectionner.

Étape 5 : ajouter un skill de connaissance si nécessaire

Si la capacité dépend d’une procédure longue, je crée un SKILL.md.

Par exemple :

  • règles de renouvellement ;
  • matrice de compatibilité ;
  • procédure de support ;
  • modèle de réponse ;
  • checklist réglementaire.

Je garde les effets de bord dans l’exécuteur .NET.

Je garde la connaissance évolutive dans le skill.

Étape 6 : tester les frontières

Je teste au minimum :

  • un prompt clairement compatible ;
  • un prompt hors domaine ;
  • un prompt ambigu ;
  • une donnée obligatoire absente ;
  • un outil en erreur ;
  • une annulation ;
  • une action à haut risque ;
  • un fournisseur qui ne respecte pas la sortie structurée.
flowchart LR
    IDEA["Nouvelle capacité"] --> CAP["CapabilitiesPrompt"]
    CAP --> EXEC["Executor .NET"]
    EXEC --> TOOL["Outils typés"]
    TOOL --> DOMAIN["Handlers métier"]
    EXEC --> DI["Enregistrement DI"]
    IDEA -->|"Connaissance longue"| SKILL["SKILL.md"]
    SKILL --> PROVIDER["AgentSkillsProvider"]
    DI --> ROUTER["Routeur commun"]
    PROVIDER --> AGENT["Agent commun"]
    ROUTER --> AGENT
    DOMAIN --> TEST["Tests"]
    AGENT --> TEST

Ce que cela apporte concrètement à Appliman

Une interface naturelle

L’utilisateur n’a pas besoin de connaître le nom d’un filtre ou d’un handler.

Il décrit son objectif.

Un contexte métier

L’agent connaît l’utilisateur, la page et l’entité.

Il peut produire une réponse située.

Une accélération des tâches répétitives

Il peut préparer un email.

Il peut chercher un prospect.

Il peut résumer un tracking.

Il peut générer un template compatible.

Une extensibilité modulaire

Chaque produit peut ajouter son exécuteur.

Chaque expertise peut devenir un skill.

Le cœur reste stable.

Une indépendance relative au fournisseur

Le domaine ne dépend pas directement d’un SDK propriétaire.

La factory porte l’adaptation.

Une meilleure gouvernance

Les outils sont explicites.

Les traces sont détaillées.

Les actions sensibles peuvent rester humaines.

La mémoire est lisible.

Une base pour de nouveaux canaux

Le moteur n’est pas lié au panneau de chat.

Les contrats d’abstraction peuvent servir :

  • une API ;
  • une application MAUI ;
  • un processus en arrière-plan ;
  • un client distant ;
  • un serveur MCP ;
  • un assistant spécialisé.

Les limites que je surveille

Je préfère les dire clairement.

Microsoft Agent Framework apporte une structure.

Il ne rend pas un système automatiquement sûr.

Il ne rend pas un mauvais outil métier fiable.

Il ne réduit pas automatiquement les coûts.

Dans mon implémentation, je surveille particulièrement :

  • le nombre d’appels LLM du pipeline complet ;
  • la taille du contexte injecté ;
  • la compatibilité des sorties structurées selon le modèle ;
  • la durée de vie du cache de sessions ;
  • l’isolation entre utilisateurs ;
  • le stockage des clés ;
  • les droits métier avant chaque outil ;
  • l’approbation des écritures sensibles ;
  • la rétention des fichiers temporaires ;
  • la future séparation entre client Blazor et moteur distant.

Je surveille aussi une tension classique.

Plus un agent possède d’outils, plus son espace de décision grandit.

Je préfère plusieurs exécuteurs spécialisés à un agent universel avec cinquante outils.

Le routeur réduit d’abord le domaine.

L’exécuteur expose ensuite seulement ses outils.

Cette hiérarchie est essentielle.

Ce que je retiens

Microsoft Agent Framework m’a permis de sortir du prototype.

Je ne raisonne plus en « prompt + réponse ».

Je raisonne en :

  • contexte ;
  • intention ;
  • plan ;
  • décision ;
  • exécuteur ;
  • outil ;
  • résultat ;
  • action UI.

La différence est profonde.

Le LLM n’est plus le centre du système.

Il devient un composant de raisonnement dans une architecture .NET.

Le domaine Appliman reste le centre.

Les skills apportent l’expertise.

Les exécuteurs apportent l’action.

Les workflows apportent le contrôle.

Blazor apporte l’expérience intégrée.

Et l’humain garde la décision finale quand elle compte. 🙂

Références