Un passage ressemble généralement à ceci :
- L’application reçoit une tâche et décide si elle doit aller vers un agent isolé ou dans un workflow.
- Le framework initialise la session et l’état associé de conversation ou d’exécution.
- L’agent appelle le model provider et, si nécessaire, invoque des tools ou MCP.
- Si la tâche est modélisée comme un workflow, les données avancent dans le graphe via des executors et des edges.
- Le middleware peut intercepter le déroulement du traitement, tandis que l’observability enregistre la telemetry et le tracing.
- Si le scénario est long, le checkpointing, la persistence ou le human-in-the-loop sont activés.
- Le système retourne non seulement du texte, mais un résultat accompagné du contexte d’exécution, de l’état et de la trace d’observabilité.
Agent Framework gère tout le cycle de vie d’un scénario agentique ou workflow.
Exemple d’agent simpleVoyons à quoi ressemble l’agent le plus simple dans Agent Framework.
Commençons par ajouter les packages nécessaires :
dotnet add package Azure.AI.Projects --prerelease dotnet add package Azure.Identity dotnet add package Microsoft.Agents.AI.Foundry --prerelease Créons maintenant un agent minimal. Dans cet exemple, l’agent reçoit le rôle d’assistant DevOps, se connecte à un modèle via Azure AI Project et répond à la tâche de l’utilisateur.
using System;using Azure.AI.Projects;using Azure.Identity;using Microsoft.Agents.AI;var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "<model name>";// Create a client for Azure AI Project.// AzureCliCredential is convenient for local development if you are already logged in via Azure CLI.var projectClient = new AIProjectClient( new Uri(endpoint), new AzureCliCredential());// Convert the Azure AI Project client into an AI agent.// The agent gets a model, a name, and instructions that define its role.AIAgent agent = projectClient.AsAIAgent( model: deploymentName, name: "DevOpsAssistant", instructions: """ You are a DevOps assistant. You analyze deployment and release issues. Keep answers concrete. Do not invent facts if the context is missing. """);// Run the agent with a user task.// RunAsync is the simplest way to send a task and get the final response.var result = await agent.RunAsync( "Find out why the release pipeline started failing after the container image was changed.");Console.WriteLine(result);Ce qu’il faut remarquer ici :
- Agent — c’est un objet applicatif auquel on attribue un rôle.
- Provider / client — c’est la couche de connexion à un backend IA concret. Dans l’exemple, on utilise AIProjectClient.
- Credential layer — c’est le mécanisme d’authentification. Ici, on utilise AzureCliCredential, pratique pour le développement local.
- Instructions — c’est le contrat du rôle. Elles expliquent à l’agent comment il doit se comporter, dans quel style répondre et ce qu’il ne doit pas faire.
- RunAsync(...) — c’est le chemin le plus court entre la tâche et la réponse. On transmet une tâche à l’agent, et il retourne un résultat.
Un tel agent convient aux scénarios simples : poser une question, analyser un problème, obtenir un brouillon d’analyse ou demander au modèle d’utiliser les tools connectés.
Par exemple, dans un scénario DevOps, un tel agent peut être utilisé pour une première analyse :
var answer = await agent.RunAsync("""The deployment failed after we changed the base image from ubuntu:22.04 to alpine.What should we check first?""");Console.WriteLine(answer);Ici, l’agent ne gère pas encore un processus complexe. Il joue simplement le rôle d’une entité qui aide l’ingénieur à comprendre la situation.
Si vous devez recevoir la réponse progressivement, sans attendre le résultat final complet, vous pouvez utiliser le mode streaming via
RunStreamingAsync(...). C’est pratique pour les UI, les chats et les réponses longues.
Exemple minimal de workflowVoyons maintenant un workflow.
Si un agent est un rôle, alors un workflow est déjà un graphe d’exécution. On y décrit explicitement les étapes, l’ordre d’exécution et le passage des données entre elles.
L’exemple ci-dessous est volontairement simple. Son objectif est de montrer la mécanique : il y a une première étape, une deuxième étape, et un lien entre les deux.
using System;using System.Threading;using Microsoft.Agents.AI.Workflows;// This function represents the first workflow step.// It normalizes the input before passing it to the next step.Func<string, string> normalize = text => text.Trim();// Bind the function as a workflow executor.// Executor is a workflow node that can receive input and produce output.var normalizeExecutor = normalize.BindAsExecutor("NormalizeExecutor");// This executor represents the second workflow step.// It checks whether the deployment plan contains a rollback strategy.class RiskTagExecutor() : Executor<string, string>("RiskTagExecutor"){ public override ValueTask<string> HandleAsync( string message, IWorkflowContext context, CancellationToken cancellationToken = default) { // This is intentionally simple demo logic. // In a real system, this could call an agent, a policy engine, or an external API. var output = message.Contains("rollback", StringComparison.OrdinalIgnoreCase) ? $"READY: {message}" : $"CHECK_MANUALLY: {message}"; return ValueTask.FromResult(output); }}RiskTagExecutor riskTag = new();// Create a workflow that starts with NormalizeExecutor.WorkflowBuilder builder = new(normalizeExecutor);// Add an edge from the first executor to the second executor.// This means the output of NormalizeExecutor becomes input for RiskTagExecutor.builder.AddEdge(normalizeExecutor, riskTag).WithOutputFrom(riskTag);// Build the workflow graph.var workflow = builder.Build();// Run the workflow in the current process.// The input goes to the first executor and then flows through the graph.await using Run run = await InProcessExecution.RunAsync( workflow, "Deploy plan includes rollback and smoke tests.");// Read workflow events.// ExecutorCompletedEvent tells us that a workflow node has finished execution.foreach (WorkflowEvent evt in run.NewEvents){ if (evt is ExecutorCompletedEvent completed) { Console.WriteLine($"{completed.ExecutorId}: {completed.Data}"); }}Ce workflow contient deux étapes.
La première étape est
NormalizeExecutor. Elle normalise le texte d’entrée. La deuxième étape est
RiskTagExecutor. Elle vérifie si le plan contient le mot
rollback. Si
rollback est présent, le résultat est marqué comme
READY. Si
rollback n’est pas trouvé, le résultat est marqué comme
CHECK_MANUALLY.
Le processus ressemble donc à ceci :
Input -> NormalizeExecutor -> RiskTagExecutor -> Output
Sur un exemple aussi simple, on voit bien la différence principale entre un workflow et un agent.
À un agent, on dirait :
« Regarde le deployment plan et décide si tout est correct ».
Dans un workflow, on définit explicitement le processus :
- d’abord normaliser les données d’entrée ;
- ensuite vérifier la présence d’une stratégie de rollback ;
- puis retourner le résultat ;
- si nécessaire, ajouter ensuite une review, une approval ou une étape suivante.
Complexifions maintenant le schéma et examinons d’autres composants du framework.
1. Tools : l’agent ne fait plus que répondre, il agit aussiSans tools, un agent ne peut que raisonner à partir du texte qu’on lui donne. Avec des tools, il peut exécuter des actions : appeler des fonctions, accéder à des API, obtenir le statut d’un pipeline, vérifier un déploiement ou lire des données externes.
Par exemple, ajoutons à l’agent un simple tool DevOps qui retourne le statut d’une release.
using System;using System.ComponentModel;using Azure.AI.Projects;using Azure.Identity;using Microsoft.Agents.AI;using Microsoft.Extensions.AI;var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";[Description("Get the release status by release id.")]static string GetReleaseStatus( [Description("Release id, for example release-1042.")] string releaseId){ // Demo implementation. In production, call Azure DevOps, GitHub Actions, or another release system. return releaseId switch { "release-1042" => "Failed: container image pull error. Registry returned unauthorized.", "release-1043" => "Succeeded.", _ => "Release was not found." };}AIAgent agent = new AIProjectClient( new Uri(endpoint), new AzureCliCredential()) .AsAIAgent( model: deploymentName, name: "DevOpsAssistant", instructions: """ You are a DevOps assistant. Use available tools when you need factual release information. Keep answers concrete and do not invent facts. """, tools: [AIFunctionFactory.Create(GetReleaseStatus)]);Console.WriteLine(await agent.RunAsync("""Analyze release-1042.Find out why it failed and suggest the first thing to check."""));Le changement architectural important ici est que l’agent n’est plus limité au texte de l’utilisateur. Il peut appeler GetReleaseStatus, obtenir un résultat factuel et former sa réponse à partir de celui-ci.
Avant, cela ressemblait à ceci :
User prompt -> Agent -> Model -> Text answer Maintenant, cela ressemble à ceci :
User prompt -> Agent -> Model decides to call a tool -> GetReleaseStatus(...) -> Tool result -> Model -> Grounded answer Pour les scénarios DevOps, c’est une différence fondamentale. L’agent peut désormais fonctionner non seulement comme « conseiller », mais aussi comme participant au processus de diagnostic.
2. Sessions et state : l’agent cesse de recommencer chaque requête à zéroLe problème suivant d’un agent simple est l’absence de contexte stable entre les appels.
Si vous appelez
RunAsync(...) sans session à chaque fois, chaque exécution est une opération séparée. Mais le troubleshooting se fait presque toujours en plusieurs étapes : l’utilisateur décrit d’abord le problème, puis apporte un log, puis précise l’erreur, puis demande de vérifier une hypothèse.
Pour cela, Agent Framework propose
AgentSession.
AgentSession session = await agent.CreateSessionAsync();Console.WriteLine(await agent.RunAsync("""Release release-1042 failed after we changed the container image.""", session));Console.WriteLine(await agent.RunAsync("""The error says: unauthorized when pulling image from the registry.What should I check next?""", session));Le deuxième appel reçoit la même session. L’agent continue donc la même conversation et peut tenir compte du contexte précédent. Selon la documentation,
AgentSession est un conteneur de l’état de conversation ; il peut contenir l’historique, de la memory ou des références à un stockage externe, et
RunAsync(...) met à jour la session avec les messages d’entrée et de sortie.
Concrètement, cela transforme l’agent d’un appel ponctuel au modèle en assistant multi-turn :
Run 1:User: Release failed after image change.
Agent: Check registry access, image tag, service connection.
Run 2 with same session:
User: Error is unauthorized when pulling image.
Agent: Since we are already investigating image pull failure, check registry credentials...
Sans session, la deuxième réponse serait moins précise, car l’agent devrait reconstruire le contexte.
3. Memory et context providers : l’agent reçoit un contexte externeLa session résout le problème de l’historique de conversation. Mais l’agent a souvent besoin non seulement du dialogue, mais aussi d’éléments factuels supplémentaires : runbook, règles de release, ownership, known issues, notes d’architecture.
Dans Agent Framework, cela passe par les context providers. Ils sont connectés à l’agent via les options et peuvent ajouter à la requête des instructions, messages ou tools supplémentaires avant l’appel au modèle. Dans la documentation, cela est décrit via AIContextProvider ; il peut stocker un état spécifique à la session dans AgentSession, et non dans l’instance du provider elle-même.
Une version simplifiée du branchement d’un context provider ressemble à ceci :
using Microsoft.Agents.AI;AIAgent agentWithContext = new AIProjectClient( new Uri(endpoint), new AzureCliCredential()) .AsAIAgent( model: deploymentName, options: new ChatClientAgentOptions() { ChatOptions = new() { Instructions = """ You are a DevOps assistant. Use provided operational context when analyzing deployment issues. """ }, AIContextProviders = [ new DeploymentRunbookContextProvider() ] });Le provider lui-même peut être un composant séparé qui ajoute le contexte opérationnel nécessaire avant l’exécution de l’agent.
using System.Threading;using Microsoft.Agents.AI;using Microsoft.Extensions.AI;internal sealed class DeploymentRunbookContextProvider : AIContextProvider{ public DeploymentRunbookContextProvider() : base(null, null) { } protected override ValueTask<AIContext> ProvideAIContextAsync( InvokingContext context, CancellationToken cancellationToken = default) { // In production, load this from Git, wiki, vector search, or an internal API. var runbook = """ Deployment policy: - Production releases require a rollback plan. - Smoke tests must run after deployment. - Registry access errors should be checked against service connection permissions. - Database migrations require manual approval. """; return new ValueTask<AIContext>(new AIContext { Messages = [ new ChatMessage( ChatRole.User, $"Operational context for this investigation:\n{runbook}") ] }); }}Désormais, l’agent reçoit non seulement la question de l’utilisateur, mais aussi un contexte d’ingénierie supplémentaire. Cela réduit fortement le risque de conseils génériques et rapproche les réponses des règles du système concret.
4. Middleware : le contrôle apparaît autour de l’agentQuand l’agent dispose de tools et d’un state, la question suivante apparaît : comment contrôler l’exécution ?
Par exemple, nous pouvons avoir besoin de :
- logger toutes les exécutions de l’agent ;
- compter le nombre de messages en entrée et en sortie ;
- interdire les actions dangereuses ;
- intercepter les tool calls ;
- mesurer la latency ;
- traiter les erreurs de manière centralisée.
Dans Agent Framework, le middleware s’ajoute via un builder :
var middlewareEnabledAgent = agent .AsBuilder() .Use(runFunc: LoggingMiddleware, runStreamingFunc: null) .Build();Le middleware lui-même pour un agent run ressemble à ceci :
using System.Diagnostics;using System.Linq;using System.Threading;using System.Threading.Tasks;using Microsoft.Agents.AI;using Microsoft.Extensions.AI;async Task<AgentResponse> LoggingMiddleware( IEnumerable<ChatMessage> messages, AgentSession? session, AgentRunOptions? options, AIAgent innerAgent, CancellationToken cancellationToken){ // Log input message count before the agent runs. Console.WriteLine($"Input messages: {messages.Count()}"); var stopwatch = Stopwatch.StartNew(); try { // Continue agent execution. var response = await innerAgent .RunAsync(messages, session, options, cancellationToken) .ConfigureAwait(false); stopwatch.Stop(); // Log output message count after the agent has completed. Console.WriteLine($"Output messages: {response.Messages.Count}"); Console.WriteLine($"Agent duration: {stopwatch.ElapsedMilliseconds} ms"); return response; } catch (Exception ex) { stopwatch.Stop(); // Log errors in one central place. Console.WriteLine($"Agent failed after {stopwatch.ElapsedMilliseconds} ms: {ex.Message}"); throw; }}Ensuite, on lance non pas l’agent initial, mais l’agent avec middleware :
Console.WriteLine(await middlewareEnabledAgent.RunAsync("""Analyze release-1042 and explain why the deployment failed."""));C’est une transition architecturale importante. Le logging, le contrôle, les policies et la gestion des erreurs ne sont plus dispersés dans les prompts et la business logic. Ils deviennent une couche séparée autour de l’agent.
5. Observability : le travail de l’agent peut être analysé après exécutionIl existe un autre niveau utile : le middleware non pas de l’exécution complète de l’agent, mais des appels de fonctions spécifiques. Par exemple, on peut logger chaque tool appelé par l’agent :
using System;using System.Threading;using System.Threading.Tasks;using Microsoft.Agents.AI;async ValueTask<object?> FunctionCallingMiddleware( AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken){ // Log the function name before execution. Console.WriteLine($"Function call: {context.Function.Name}"); var result = await next(context, cancellationToken); // Log the function result after execution. Console.WriteLine($"Function result: {result}"); return result;}Il se branche ainsi :
var agentWithFunctionMiddleware = agent .AsBuilder() .Use(FunctionCallingMiddleware) .Build();Console.WriteLine(await agentWithFunctionMiddleware.RunAsync("""Check release-1042 and explain the failure."""));C’est particulièrement utile lorsque les tools appellent des systèmes externes : Azure DevOps, GitHub, Kubernetes, ServiceNow, API internes. Vous voyez quel tool a été appelé, avec quel résultat, et vous pouvez ajouter une politique de sécurité ou une approval avant les actions dangereuses. Microsoft décrit séparément le function calling middleware comme un mécanisme d’interception des appels de fonctions ; pour poursuivre l’exécution, le middleware doit appeler le next transmis.
Comment l’architecture changeAgent minimal :
User -> Agent -> Model -> Answer Agent avec tools, session et middleware :
User -> Agent run middleware -> AgentSession -> Context providers -> Agent -> Model -> Function calling middleware -> Tools -> Tool result -> Model -> Answer Et c’est ici qu’Agent Framework devient intéressant non pas comme « encore une couche autour d’un LLM », mais comme un cadre d’ingénierie.
Les tools donnent à l’agent la capacité d’agir.
La session permet de poursuivre le travail en plusieurs étapes. Les context providers ajoutent la factographie nécessaire depuis des sources externes.
Le middleware apporte le contrôle, le logging, la sécurité et la gestion des erreurs.
L’observability permet ensuite d’analyser tout ce travail comme un vrai système de production : quels appels ont été faits, où l’erreur est apparue, quel tool a fonctionné, combien de temps a pris chaque étape.
Au final, l’agent cesse d’être simplement un prompt avec un nom. Il devient un composant applicatif contrôlé, autour duquel on peut construire de vrais scénarios DevOps, support, release management et automation.
Comment construire progressivement un système multi-agent sur Microsoft Agent Framework