AI

AgentOps : contenir une dérive de consommation d'un agent IA avant de couper la production

Un runbook de production pour attribuer une hausse de tokens et d'appels d'outils, isoler l'amplification, appliquer un budget d'exécution et valider ou rollbacker la version d'un agent IA.

04 août 2026 aiagentopsagentsobservabilitycosttokenstoolsguardrailskqlautomationrunbookrollbackproduction

Une nouvelle version d’un agent interne est mise en production. Le trafic utilisateur reste stable et les réponses arrivent encore, mais la consommation augmente rapidement : plus de tokens par intention, davantage d’appels d’outils et des conversations qui s’allongent. Couper tout le service arrête la dépense, mais supprime aussi les parcours sains et les traces nécessaires au diagnostic.

Ce runbook traite la dérive comme un incident d’exploitation. L’objectif est d’attribuer la consommation à une version, un parcours et une étape, puis de contenir l’amplification au plus petit périmètre. Le point de sortie doit être explicite : conserver la version avec un budget corrigé, revenir à la configuration précédente ou maintenir le parcours en lecture seule jusqu’à correction.

Figer la chronologie et l’unité de travail

Une facture agrégée par jour ne permet pas de comprendre une boucle de quelques minutes. Avant de modifier le modèle ou le prompt, relevez l’heure du premier écart, les versions actives et une unité de travail stable : l’intention. Une intention peut traverser plusieurs tours, appels de modèle et appels d’outils ; c’est ce regroupement qui révèle l’amplification.

yaml agent-consumption-incident.yml
incident_window:
start: <timestamp>
end: <timestamp>

active_versions:
agent: <agent-version>
prompt: <prompt-version>
model_deployment: <deployment-name>
tool_catalog: <catalog-version>
retrieval_index: <index-version>

unit_of_work:
intent_id: <stable-intent-id>
conversation_id: <conversation-id>
tenant_or_audience: <bounded-segment>

evidence:
- input_and_output_tokens_per_model_call
- tool_calls_and_retries_per_intent
- retrieved_items_and_bytes
- context_size_before_each_call
- latency_result_and_error_class
- release_and_configuration_timeline

Ne mélangez pas identifiants de corrélation et contenu sensible. Les prompts, sorties d’outils et documents récupérés peuvent contenir des données métier ; journalisez leurs tailles, versions et empreintes lorsque le texte complet n’est pas nécessaire au diagnostic.

Séparer trafic, coût unitaire et amplification

La consommation totale est le produit de plusieurs phénomènes. Une hausse d’utilisateurs est différente d’une hausse de tokens par appel. Une hausse de tokens par intention peut elle-même venir d’un contexte qui grossit, d’un nombre d’étapes supérieur ou de retries invisibles dans la conversation.

text consumption-decomposition.txt
Consommation totale
intentions
x appels de modele par intention
x tokens par appel

Charge outils
intentions
x appels d'outils par intention
x cout et duree de chaque outil

Signaux d'amplification
trafic stable mais appels de modele par intention en hausse
intentions stables mais appels d'outils ou retries en hausse
sorties d'outils stables mais contexte retransmis a chaque tour
meme intention recreee apres timeout ou reprise client
parcours read-only qui continue apres resultat suffisant

Comparez la version suspecte à une période de référence comparable par parcours, audience et résultat. Une moyenne globale peut masquer un seul tenant ou un seul outil très coûteux. Les seuils doivent venir de cette distribution réelle ; un nombre arbitraire copié d’un autre agent ne constitue pas un budget.

Émettre une télémétrie exploitable par intention

Un événement de consommation doit relier appel de modèle, outil, version et résultat sans dépendre du texte du prompt. Gardez des compteurs additifs afin de reconstruire le total même si une trace est échantillonnée ailleurs.

json agent-consumption-event.json
{
"timestamp": "<timestamp>",
"intentId": "<intent-id>",
"conversationId": "<conversation-id>",
"agentVersion": "<agent-version>",
"promptVersion": "<prompt-version>",
"modelDeployment": "<deployment-name>",
"stage": "plan|retrieve|tool|synthesize",
"modelCallId": "<call-id>",
"inputTokens": 0,
"outputTokens": 0,
"contextBytes": 0,
"retrievedItems": 0,
"toolName": "<tool-or-empty>",
"toolCallId": "<tool-call-id-or-empty>",
"toolAttempt": 0,
"result": "success|denied|timeout|error|budget_exhausted",
"durationMs": 0
}

Le prix peut évoluer ou dépendre du déploiement. Conservez d’abord les unités techniques et la version tarifaire utilisée par votre calcul de coût. Cela permet de distinguer une dérive de comportement d’un changement de prix ou de modèle.

Localiser l’amplification avec KQL

La requête suivante suppose une table normalisée AgentConsumptionEvents. Adaptez les noms à votre pipeline et fixez une fenêtre d’incident ; ne lancez pas une agrégation non bornée pendant la crise.

kusto 01-agent-consumption-by-intent.kql
let StartTime = datetime(2026-08-04T08:00:00Z);
let EndTime = datetime(2026-08-04T10:00:00Z);
AgentConsumptionEvents
| where TimeGenerated between (StartTime .. EndTime)
| summarize
  ModelCalls=dcountif(ModelCallId, isnotempty(ModelCallId)),
  ToolCalls=dcountif(ToolCallId, isnotempty(ToolCallId)),
  ToolAttempts=countif(isnotempty(ToolCallId)),
  InputTokens=sum(InputTokens),
  OutputTokens=sum(OutputTokens),
  MaxContextBytes=max(ContextBytes),
  RetrievedItems=sum(RetrievedItems),
  Failures=countif(Result in ("timeout", "error")),
  BudgetStops=countif(Result == "budget_exhausted")
by IntentId, AgentVersion, PromptVersion, ModelDeployment
| extend TotalTokens = InputTokens + OutputTokens
| order by TotalTokens desc

Examinez ensuite les intentions les plus lourdes étape par étape. Si InputTokens progresse à chaque tour alors que la source ne change pas, inspectez l’accumulation de contexte. Si ToolAttempts dépasse nettement ToolCalls, inspectez les retries. Si RetrievedItems augmente après une release, vérifiez les filtres et limites du retrieval. Le but est d’identifier le multiplicateur, pas seulement le plus gros total.

Contenir au plus petit périmètre

Un arrêt global est justifié si vous ne pouvez plus attribuer les actions ou si les écritures métier sont incontrôlées. Sinon, réduisez d’abord le blast radius : bloquez une version, un parcours, un outil, une audience ou les nouvelles intentions, tout en conservant la lecture et les diagnostics.

yaml agent-runtime-budget-policy.example.yml
policy_version: <version>
scope:
agent: ops-assistant
environment: production

per_intent:
max_model_calls: <derived-from-baseline>
max_total_tokens: <derived-from-baseline>
max_tool_calls: <derived-from-baseline>
max_retrieved_items: <derived-from-baseline>
max_elapsed_time: <derived-from-slo>

on_budget_exhausted:
stop_new_model_and_tool_calls: true
allow_final_bounded_response: true
disable_write_tools: true
emit_reason_and_correlation_id: true

containment:
quarantine_agent_versions: []
read_only_tool_allowlist: []
blocked_audiences: []
pin_previous_prompt_version: false

Le budget doit être appliqué par le runtime ou la passerelle d’exécution, pas confié au prompt. L’agent peut annoncer qu’il va s’arrêter ; seule une couche déterministe peut refuser l’appel suivant. Réservez un petit budget pour produire une réponse finale exploitable, sans autoriser de nouvel outil.

Diagnostiquer le multiplicateur avant d’optimiser le modèle

Suivez le parcours le plus coûteux dans l’ordre. Un contexte volumineux peut venir d’une sortie d’outil réinjectée intégralement, d’un historique jamais résumé ou de documents dupliqués. Des appels supplémentaires peuvent venir d’une condition de sortie perdue, d’un plan qui se régénère, d’un timeout client ou d’une erreur classée à tort comme retentable.

Vérifiez aussi les frontières :

  • le client recrée-t-il une intention lorsqu’il ne reçoit pas la réponse à temps ?
  • le runtime reprend-il une trace tout en rejouant les étapes déjà terminées ?
  • l’outil pagine-t-il réellement ou renvoie-t-il tout le jeu de résultats ?
  • une sortie d’outil est-elle stockée puis réinjectée plusieurs fois ?
  • le changement concerne-t-il le prompt, le modèle, le catalogue d’outils, le retrieval ou plusieurs éléments à la fois ?

Changer immédiatement de modèle peut réduire le prix unitaire tout en laissant la boucle intacte. La correction durable supprime le multiplicateur et garde un budget qui limite sa réapparition.

Tester le budget et le chemin de dégradation

Rejouez un corpus de traces représentatives sans action d’écriture. Injectez un résultat d’outil volumineux, une pagination excessive, un timeout et une réponse incomplète. Le test doit prouver que chaque intention s’arrête, qu’un résultat partiel est explicable et qu’aucune écriture n’est tentée après épuisement.

yaml agent-consumption-evaluation.yml
cases:
- id: normal_multi_step_intent
  expected:
    result: success
    budget_exhausted: false

- id: oversized_tool_result
  fault: tool_returns_bounded_but_large_payload
  expected:
    context_compacted_or_rejected: true
    repeated_full_payload: false

- id: retry_amplification
  fault: tool_timeout_after_accept
  expected:
    duplicate_write: false
    budget_stops_additional_attempts: true

- id: model_loop
  fault: exit_condition_never_selected
  expected:
    runtime_stops_execution: true
    final_response_names_budget_exhaustion: true
    write_tools_after_stop: 0

Déployez d’abord la policy en mode observation, puis sur un canary read-only. Comparez tokens et appels par intention, taux de tâches terminées, refus de budget et latence. Une baisse de consommation n’est pas une réussite si les parcours légitimes sont tronqués ou si les opérateurs perdent la preuve de l’état final.

Valider, rollbacker ou maintenir la quarantaine

Versionnez ensemble le prompt, le déploiement de modèle, le catalogue d’outils, le retrieval et la policy de budget. Un rollback partiel peut rétablir un prompt qui attend un outil absent ou conserver un index incompatible avec le parcours précédent.

Conservez la nouvelle version si le canary retrouve une distribution comparable à la référence, termine les cas critiques et n’exécute aucune action après épuisement. Rollbackez vers le bundle précédent si l’amplification suit la nouvelle version ou si le budget coupe des parcours normaux. Maintenez la quarantaine read-only si l’état métier d’appels passés reste ambigu ; le rollback de configuration n’annule pas une action déjà acceptée par un backend.

Conclusion

Une dérive de consommation AgentOps n’est pas seulement un problème de facture. Elle révèle souvent qu’une intention n’est plus bornée : contexte accumulé, retrieval trop large, retry, condition de sortie ou reprise mal contrôlée.

La décision de production repose sur une preuve par intention. Gardez la version si le budget, le canary et les résultats métier sont stables. Sinon, épinglez le bundle précédent, bloquez les écritures de la version suspecte et conservez assez de télémétrie pour corriger le multiplicateur avant la reprise.