e-naxosMAF COURSE / COURS
English

MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1

Graphes typés et événements

Décrire le chemin par des messages typés et vérifier la sortie métier du graphe.

Objectifs

Qui choisit le chemin ?

Une demande de backlog arrive avec un composant inconnu. Le programme doit demander une précision avant toute revue. Video2 posait cette question avec un ancien scénario B2B ; la base actuelle examine un BacklogDraft déjà fourni par l’hôte. Elle ne classe pas du texte libre et ne crée aucune ligne de backlog. Le graphe rend les chemins possibles explicites, puis les événements montrent le chemin effectivement suivi.

Choisir une orchestration
MécanismePortée
C# ordinaireRègles fixes et appels explicites : Demo260.
Workflow natifExécuteurs, messages et arêtes : Demo230–250.
Plan JSONDonnée proposée par le modèle : Demo270–290.

Contrats et branches exhaustives

Executor<TInput,TOutput> déclare les messages échangés. AnalyzeDraft copie le tableau des questions, appelle Validate et produit DraftDecision. IsReady dépend des erreurs calculées par l’hôte. Les deux conditions se complètent : un brouillon incomplet atteint ClarifyDraft, un brouillon valide atteint AcceptDraft ou PrepareReview. La clarification produit NeedsClarification comme résultat terminal ; elle ne relance pas automatiquement le dialogue.

var analyze = new AnalyzeDraft();
var clarify = new ClarifyDraft();
var builder = new WorkflowBuilder(analyze)
    .AddEdge<DraftDecision>(analyze, clarify, condition: d => d is { IsReady: false });
if (telemetry) builder.WithOpenTelemetry();
if (reviewer is null)
{
    var ready = new AcceptDraft();
    return builder.AddEdge<DraftDecision>(analyze, ready, condition: d => d is { IsReady: true })
        .WithOutputFrom(clarify, ready).Build();

shared/Maf.Workflows/BacklogWorkflow.cs · L43–L52 · 9a895c94534c

Code source · Maf.Workflows · Dossier

Chemins possibles du brouillon
  1. BacklogDraft → AnalyzeDraft → DraftDecision
  2. Erreurs ou questions → ClarifyDraft → NeedsClarification
  3. Valide → AcceptDraft → Ready (Demo230)
  4. Valide → PrepareReview → agent natif → ValidateReview → Reviewed ou Rejected

Insérer un agent natif et garder la frontière

Dans la version liée de MAF, BindAsExecutor adapte un véritable ChatClientAgent au graphe. Les anciennes diapositives qui annonçaient cette intégration pour plus tard sont dépassées pour cette base. PrepareReview envoie ChatMessage puis TurnToken, avec deux déclarations SendsMessage. Le premier message est mis en attente par l’hôte natif ; le token déclenche le tour. Deux livraisons au même exécuteur peuvent donc représenter un seul appel au modèle.

var nativeAgent = reviewer.BindAsExecutor(new AIAgentHostOptions
{
    ForwardIncomingMessages = false,
    EmitAgentUpdateEvents = false,
    EmitAgentResponseEvents = true
});
return builder.AddEdge<DraftDecision>(analyze, prepare, condition: d => d is { IsReady: true })
    .AddEdge(prepare, nativeAgent)
    .AddEdge(nativeAgent, validate)
    .WithOutputFrom(clarify, validate).Build();

shared/Maf.Workflows/BacklogWorkflow.cs · L57–L66 · 9a895c94534c

Code source · Maf.Workflows · Dossier

[SendsMessage(typeof(ChatMessage))]
[SendsMessage(typeof(TurnToken))]
private sealed class PrepareReview() : Executor<DraftDecision>("PrepareReview")
{
    public BacklogDraft? Expected { get; private set; }

    public override async ValueTask HandleAsync(DraftDecision message, IWorkflowContext context,
        CancellationToken cancellationToken = default)
    {
        Expected = message.Draft;
        await context.SendMessageAsync(new ChatMessage(ChatRole.User, JsonSerializer.Serialize(Expected)), cancellationToken: cancellationToken);
        await context.SendMessageAsync(new TurnToken(emitEvents: false), cancellationToken: cancellationToken);

shared/Maf.Workflows/BacklogWorkflow.cs · L110–L121 · 9a895c94534c

Code source · Maf.Workflows · Dossier

ForwardIncomingMessages=false empêche le JSON utilisateur de devenir une réponse générée en aval. ValidateReview ne conserve que les messages Assistant puis contrôle JSON, domaines et égalité de tous les faits avec le snapshot hôte. Une priorité modifiée reste rejetée même si le JSON est valide. Reviewed ne signifie ni approbation humaine ni écriture. Cette recopie stricte sert à enseigner la frontière ; une application réelle n’a pas besoin d’un modèle pour recopier un contrat valide.

public static ReviewResult ValidateResponse(string text, BacklogDraft expected)
{
    BacklogDraft? draft;
    try { draft = JsonSerializer.Deserialize<BacklogDraft>(text, new JsonSerializerOptions(JsonSerializerDefaults.Web)); }
    catch (JsonException) { return new("Rejected", null, ["Agent response is not a BacklogDraft JSON object."]); }
    if (draft is null) return new("Rejected", null, ["Missing agent draft."]);
    var errors = draft.Validate();
    if (errors.Length != 0) return new("Rejected", null, errors);
    if (draft.Title != expected.Title || draft.Kind != expected.Kind || draft.Priority != expected.Priority
        || draft.Component != expected.Component || draft.Summary != expected.Summary
        || !draft.ClarifyingQuestions.SequenceEqual(expected.ClarifyingQuestions))
        return new("Rejected", null, ["The agent changed the supplied business facts."]);
    return new("Reviewed", draft, ["Host validation passed; no write tool is exposed."]);

shared/Maf.Workflows/BacklogWorkflow.cs · L70–L82 · 9a895c94534c

Code source · Maf.Workflows · Dossier

Événements, rôles et preuves

Demo250 consomme tout WatchStreamAsync. ExecutorInvokedEvent décrit les livraisons réelles ; AgentResponseEvent expose la réponse brute ; seul WorkflowOutputEvent portant ReviewResult alimente les résultats métier. Les spans Activity natifs activés par WithOpenTelemetry mesurent causalité et durée, sans collecteur externe. Ni un événement ni un span ne prouve une ligne enregistrée. Le streaming des événements du workflow reste actif alors que les mises à jour de tokens de l’agent sont désactivées.

await foreach (var evt in run.WatchStreamAsync(stop.Token))
{
    eventNames.Add(evt.GetType().Name);
    if (observe) Console.WriteLine("Event: " + evt.GetType().Name);
    if (evt is ExecutorInvokedEvent invoked) path.Add(invoked.ExecutorId);
    if (evt is WorkflowErrorEvent error) errors.Add(error.Exception?.Message ?? "Unknown workflow error.");
    if (evt is WorkflowOutputEvent { Data: ReviewResult result }) results.Add(result);

shared/Maf.Workflows/WorkflowDemo.cs · L46–L52 · 9a895c94534c

Code source · Maf.Workflows · Dossier

Demo260 crée BacklogAgent et KnowledgeAgent avec deux sessions distinctes, puis Route choisit un seul agent. Les préfixes français où/comment/pourquoi et les chaînes 401, 500, logs ou rétention vont à Knowledge ; le reste va à Backlog. Les sessions vivent en mémoire et ne partagent pas de store métier. Ce routage lexical C# est observable et limité : une question anglaise sans ces signaux peut aller à Backlog. Il ne s’agit ni d’un classificateur sémantique ni d’un graphe MAF.

static RouteTarget Route(string input)
{
    var s = input.Trim().ToLowerInvariant();
    if (s.StartsWith("où") || s.StartsWith("comment") || s.StartsWith("pourquoi"))
        return RouteTarget.Knowledge;
    if (s.Contains("401") || s.Contains("500") || s.Contains("logs") || s.Contains("rétention"))
        return RouteTarget.Knowledge;
    return RouteTarget.Backlog;
}

enum RouteTarget { Backlog, Knowledge }

demos/Demo260_MultiAgentRoles/Program.cs · L45–L55 · 9a895c94534c

Code source · Demo260 · Dossier

Prédire puis vérifier une branche

dotnet run --project demos/Demo230_TypedWorkflow/Demo230.App.csproj -c Release --no-build -- --incomplete

Depuis la distribution complète compilée, prédire AnalyzeDraft → ClarifyDraft, puis contrôler NeedsClarification et l’absence d’appel modèle. Répéter avec --invalid pour le composant Unknown. Dans les tests, ClarificationSkipsNativeAgentEntirely mesure zéro appel et NativeAgentRunsOnceWithSchemaAndHostPostValidation mesure un seul appel sur la branche prête, avec un transport scripté.

dotnet test tests/Maf.Workflows.Tests/Maf.Workflows.Tests.csproj -m:1 -c Release --no-build --no-restore

Portée de ce graphe

Construire un graphe neuf par entrée indépendante. PrepareReview garde un snapshot dans un champ local non sérialisé : ce graphe n’est pas un service concurrent réutilisable ni un exemple de checkpoint durable. Les variantes natives utilisent le profil chat commun, sans embeddings, et ne basculent pas automatiquement vers une API payante. Les tests scriptés ne qualifient pas granite4:3b ni un autre fournisseur.

Du chemin au temps

Prérequis

Pour poursuivre

Précédent

Suivant

lot009-workflows-1 · 2026-10-24 · 9a895c94534cd5fc6f3da883e37e5f7856e588c1