MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1
Télémétrie et usage
Relier une exécution à ses spans, interpréter les compteurs partiels et exporter uniquement les métadonnées autorisées.
Objectifs
- Spans et corrélation
- Usage nullable et partiel
- Estimation, tokens et coût
- Données sensibles et limites
Poser une question vérifiable
Une réponse lente peut venir du modèle, d’un deuxième tour d’outil, du réseau ou du workflow. Une phrase finale ne permet pas de les départager. Instrumenter consiste à observer ces étapes avec des identifiants et des durées, puis à poser une question limitée : où le temps est-il passé et quelle étape a échoué ? La réponse ne démontre ni la justesse du texte ni un effet métier persistant.
| Mécanisme | Portée |
|---|---|
| Workflow event | Progression des exécuteurs et sortie du graphe |
| SafeSpan | Parenté, durée et état technique |
| BusinessJournal | Chronologie persistée et lien vers une action locale |
Construire et lire la corrélation
ActivitySource démarre la racine execution. Ses descendants reprennent TraceId ; chaque span possède SpanId et ParentSpanId. Lire d’abord la racine, retrouver les enfants par leur parent, puis comparer état et durée. Des durées imbriquées se recouvrent : les additionner compterait plusieurs fois le même temps. Une racine en erreur ne signifie pas qu’une transaction déjà validée a été annulée.
public static TracerProvider CreateProvider(Action<SafeSpan> write) => Sdk.CreateTracerProviderBuilder()
.AddSource("Maf.Renovation", "Maf.NativeAgent", "Microsoft.Agents.AI*", "Microsoft.Extensions.AI*")
.AddHttpClientInstrumentation()
.AddProcessor(new SimpleActivityExportProcessor(new SafeExporter(write))).Build();
/// <summary>Adds the native MAF decorator with sensitive data explicitly disabled.</summary>
public static AIAgent Instrument(AIAgent agent) => agent.AsBuilder()
.UseOpenTelemetry(sourceName: "Maf.NativeAgent", configure: options => options.EnableSensitiveData = false).Build();
Code source · Maf.Observability · Dossier
Le décorateur MAF natif UseOpenTelemetry désactive explicitement les données sensibles. Le workflow utilise WithOpenTelemetry et HTTP son instrumentation sortante. L’hôte ajoute execution, model, workflow et tool. Le mode déterministe de Demo420 exerce le workflow et SQLite sans requête modèle : l’absence de span HTTP ou agent dans ce mode est cohérente. Le mode réel exige un fournisseur disponible et un appel effectif de get_review_policy avant le workflow.
Conserver les inconnues dans les usages
ObservedChatClient observe sous la boucle native d’outils. Chaque requête de transport produit un rapport ; un second appel après résultat outil doit rester visible. En flux, les deltas d’usage sont agrégés une seule fois à la fin. Échec, annulation ou abandon laissent l’usage final inconnu, y compris si le fournisseur a continué à travailler à distance. L’observateur ne relance pas les requêtes.
var input = usage?.InputTokenCount;
var output = usage?.OutputTokenCount;
long? derived = input.HasValue && output.HasValue ? checked(input.Value + output.Value) : null;
decimal? cost = rate is not null && input.HasValue && output.HasValue
? (input.Value * rate.InputPerMillion + output.Value * rate.OutputPerMillion) / 1_000_000m : null;
return new(input, output, usage?.TotalTokenCount, derived,
contextCharacters.HasValue ? ((long)contextCharacters.Value + 3) / 4 : null,
cost, rate?.Currency, rate?.Version);
Code source · Maf.Observability · Dossier
| Mécanisme | Portée |
|---|---|
| Usage absent ; contexte 400 caractères | Input=null ; Output=null ; DerivedTotal=null ; CharacterEstimate=100 |
| Input=0 ; Output=0 | DerivedTotal=0 : zéro explicitement rapporté |
| Input=7 ; Output absent | Output=null ; DerivedTotal=null |
| Input=10 ; Output=20 ; ReportedTotal=33 | DerivedTotal=30 ; conserver aussi 33, sans correction silencieuse |
Ces valeurs proviennent des fixtures MissingUsageIsNotZero et TotalsAndCostsAreAttributed de Maf.Observability.Tests ; elles illustrent le contrat du calculateur. Le rapport fournisseur et la somme dérivée restent deux champs distincts. La formule (caractères + 3) / 4 est une heuristique entière sur le contexte fourni ; elle exclut les consignes et la question lorsqu’elles ne font pas partie de ce texte.
Afficher la couverture avant un total
int inputs = requests.Count(r => r.Input.HasValue);
int outputs = requests.Count(r => r.Output.HasValue);
long knownInput = requests.Where(r => r.Input.HasValue).Sum(r => r.Input!.Value);
long knownOutput = requests.Where(r => r.Output.HasValue).Sum(r => r.Output!.Value);
return new(requests.Count, inputs, outputs, knownInput, knownOutput,
inputs == requests.Count && inputs > 0 ? knownInput : null,
outputs == requests.Count && outputs > 0 ? knownOutput : null);
Code source · Maf.Observability · Dossier
UsageLedger affiche combien de requêtes ont un compteur connu, leurs sous-totaux et les totaux complets nullable. Dans la fixture LedgerSeparatesKnownSubtotalsAndCompleteTotals, les requêtes (Input=0, Output=2) et (Input=5, Output=null) donnent KnownOutputSubtotal=2 mais CompleteOutputTotal=null. Sans requête, le total complet est aussi null : aucun appel gratuit n’a été mesuré.
Aucun tarif n’est configuré par défaut. TokenRate porte deux tarifs non négatifs par million, une devise et une version/source fournies par l’appelant. EstimatedCost exige les compteurs réels Input et Output ; il applique cette hypothèse tarifaire, sans devenir une facture. Les catégories cache/raisonnement, GPU, électricité et hébergement ne sont pas tarifées. Une latence basse ou un contexte court ne suffit pas à annoncer une économie.
Exemple purement fictif de TotalsAndCostsAreAttributed : Input=10 et Output=20 avec TokenRate(1, 2, "TEST", "fixture-only-2026-10-24") donnent (10×1 + 20×2)/1 000 000 = 0,00005 TEST. Ces tarifs de fixture ne sont ceux d’aucun fournisseur. Sans les deux compteurs réels, ce calcul reste null même si une estimation de caractères est disponible.
Réduire les données avant export
public static SafeSpan Project(Activity activity) => new(
activity.Source.Name == Source.Name ? "host" : activity.Source.Name.StartsWith("System.Net.Http", StringComparison.Ordinal)
? "http" : activity.Source.Name.StartsWith("Microsoft.Agents.AI.Workflows", StringComparison.Ordinal)
? "maf.workflow" : activity.Source.Name.StartsWith("Microsoft.Extensions.AI", StringComparison.Ordinal)
? "maf.model" : "maf.agent",
activity.Source.Name == Source.Name && activity.OperationName is "execution" or "tool" or "workflow" or "model"
? activity.OperationName : "operation",
activity.TraceId.ToString(), activity.SpanId.ToString(), activity.ParentSpanId.ToString(),
activity.Duration.TotalMilliseconds, activity.Status.ToString());
Code source · Maf.Observability · Dossier
SafeSpan est une projection fermée : catégorie de source et d’opération, identifiants, durée et état. Elle écarte noms arbitraires, tags, baggage, événements, URL, attributs de ressource, messages d’exception et piles. Un outil ou un endpoint peut porter un secret dans son nom ; filtrer uniquement les prompts ne suffirait donc pas. Ne pas remplacer ce seul exporteur installé par ConsoleExporter brut ou OTLP sans filtre.
Délimiter ce qui a été observé
La garantie concerne cet exporteur ; d’autres listeners en mémoire, logs applicatifs ou transcripts console ont leur propre politique. Les réponses métier affichées au lecteur ne deviennent pas des traces sûres en redirigeant toute la console. Un export échantillonné peut perdre des étapes. Aucun collecteur, Docker ou compte cloud n’est requis ; la production demande sa propre qualification de couverture, accès et rétention.
Exercer la lecture
dotnet test tests/Maf.Observability.Tests/Maf.Observability.Tests.csproj -m:1 -c Release --no-build --no-restorePrérequis
Pour poursuivre
Précédent
Suivant
