Observabilité des agents LLM : traces, sessions, 10 problèmes
Sommaire
- Que sont les traces, les spans et les sessions d’un agent LLM ?
- Quelle vue utiliser pour quelle question ?
- Pourquoi un agent LLM est-il lent ?
- Pourquoi un agent LLM coûte-t-il cher ?
- Comment déboguer un agent LLM à partir d’un signalement utilisateur ?
- Peut-on tracer un agent de programmation sans l’avoir développé ?
- Un modèle peut-il lire les traces à votre place ?
- Que doit journaliser chaque agent ?
- Que ne couvre pas le traçage côté passerelle ?
- FAQ
- Qu’est-ce que l’observabilité des agents LLM ?
- À lire aussi
L’observabilité d’un agent LLM commence par deux façons de regrouper ses appels aux modèles : la trace, qui représente une exécution sur une chronologie, et la session, qui rassemble une conversation et son coût total. Trois en-têtes ajoutés à chaque appel suffisent à une passerelle LLM pour construire ces deux vues à partir de son journal de requêtes, sans SDK de traçage. Cette passerelle est un proxy placé entre votre code et les fournisseurs de modèles, qui journalise chaque requête. Nous avons testé ce dispositif sur un petit agent de support et sur Claude Code, puis reproduit dix problèmes courants : trois qui ralentissent l’agent, quatre qui font grimper son coût et trois qui le font échouer.
TL;DR
- Trois en-têtes de requête (
X-Trace-Id,X-Span-Id,X-Session-Id) transforment le journal de la passerelle Synthorai en traces et sessions, sans SDK de traçage. - Lancer ensemble trois vérifications indépendantes confiées à des sous-agents a ramené leur durée médiane de 3.7 s à 1.8 s, à coût identique.
- Déplacer un horodatage qui occupait la première ligne du prompt sur GPT-6 Luna a fait passer le taux de succès du cache de 0% à 73%, en partant d’un cache vide.
- Une exécution de Claude Code a coûté $0.1728. Le premier appel de chacun de ses trois agents représentait 72% du total.
Cet article présente une série de sept guides. Chaque section résume un guide, avec ses chiffres et un lien vers le code et les captures d’écran. Toutes les exécutions datent des 10 et 11 octobre 2026. Les latences sont les médianes de cinq exécutions ; tous les autres chiffres proviennent d’une seule exécution. Le code est disponible sur github.com/synthorai-io/use-cases.
Que sont les traces, les spans et les sessions d’un agent LLM ?
Une trace regroupe tout ce qu’un agent fait pour traiter un message utilisateur ou une tâche. Un span représente une étape de cette trace, et une session couvre toute la conversation, composée de plusieurs traces. Dans notre agent de support, une conversation client de deux tours correspond à une session contenant deux traces. Chaque trace comporte cinq ou six appels aux modèles. Certains proviennent d’un sous-agent : un second agent que l’agent principal lance pour traiter une partie de la tâche, avec son propre prompt et souvent un modèle moins cher.
| Terme | Périmètre | En-tête | Réutiliser la même valeur pour |
|---|---|---|---|
| Trace | une exécution de l’agent | X-Trace-Id | tous les appels de l’exécution |
| Span | un appel au modèle dans l’exécution | X-Span-Id | cet appel uniquement |
| Session | une conversation | X-Session-Id | tous les appels de la conversation |
| Agent | le sous-agent à l’origine d’un appel | X-Agent-Id | tous les appels de ce sous-agent |
| Labels | utilisateur, fonctionnalité, version | X-Synthorai-Metadata | les appels à regrouper ou à filtrer |
Vous choisissez les identifiants : ce sont des chaînes de 128 caractères ASCII imprimables au maximum. Les labels forment un objet JSON à plat dont les valeurs sont des chaînes, avec au plus 10 labels par requête et 128 octets par valeur. La passerelle renvoie les identifiants dans la réponse et ne transmet aucun de ces en-têtes au fournisseur du modèle. Avec le SDK Python d’OpenAI, les en-têtes propres à une requête se passent dans extra_headers :
resp = client.chat.completions.create(
model="gpt-6-luna",
messages=messages,
extra_headers={
"X-Session-Id": session_id, # one per conversation
"X-Trace-Id": trace_id, # one per user message
"X-Span-Id": "plan", # one per call
"X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
},
)
Si votre agent envoie déjà des identifiants de trace via un autre outil, vous n’aurez peut-être rien à ajouter. Pour chacun des trois identifiants absent d’une requête, la passerelle lit l’en-tête équivalent d’OpenTelemetry, LiteLLM, Langfuse, Open WebUI, Claude Code ou de l’OpenAI Codex CLI. OpenTelemetry est le standard ouvert de traçage dont l’en-tête traceparent transporte un identifiant de trace. Le guide Tracer un agent LLM avec 3 en-têtes HTTP construit l’agent de démonstration étape par étape : un appel de planification sur GPT-6 Luna, deux outils locaux, trois vérifications en parallèle sur DeepSeek V4.1 Flash, puis un appel pour la réponse. Son exécution de démonstration a généré 21 appels aux modèles pour $0.0017.
Quelle vue utiliser pour quelle question ?
Chaque question de la série trouve sa réponse dans une vue de la console ou dans une façon de consulter le journal depuis un outil externe. La console affiche chaque trace sous forme de diagramme en cascade : une barre par appel au modèle, sur un axe temporel commun. Les appels parallèles se chevauchent ; ceux qui s’attendent les uns les autres dessinent un escalier.
| Question | Où chercher | Guide |
|---|---|---|
| Pourquoi cette exécution a-t-elle pris 8 secondes ? | le résumé de la trace : temps dans la passerelle et temps dans l’application | Latence des agents |
| Quel tour de cette conversation a coûté le plus cher ? | la page de session, avec une ligne par trace | Coût des agents |
| Pourquoi le taux de succès du cache est-il à 0% ? | la colonne du cache sur la page de session | Coût des agents |
| Que s’est-il passé hier avec la requête de cet utilisateur ? | une recherche par label dans la liste des traces | Débogage des agents |
| Quel sous-agent de Claude Code a consommé le budget ? | le tableau des agents sur la page de session | Coût de Claude Code par sous-agent |
| Peut-on poser ces questions dans une fenêtre de chat ? | un connecteur MCP dans Claude | Interroger Claude sur vos logs LLM |
| Mon programme peut-il analyser le journal ? | les outils MCP ou l’API REST | Donner à votre agent des outils d’accès aux logs |
Chaque page de trace possède aussi un bouton de comparaison. Il affiche deux traces sur la même page, associe les appels par leur nom et place les deux diagrammes sur un axe temporel commun. Nous avons vérifié chaque correction de la série de cette façon, en comparant l’exécution avant et après modification.
Pourquoi un agent LLM est-il lent ?
Commencez par la répartition affichée en haut de la trace. Elle décompose le temps écoulé, celui qu’attend l’utilisateur entre la première requête et la dernière réponse, en deux parties : le temps dans la passerelle, pendant lequel au moins une requête au modèle est en cours, et le temps dans l’application, pendant lequel seul votre code s’exécute. Dans la trace de démonstration, les 8.5 s se répartissaient en 7.0 s dans la passerelle et 1.6 s dans l’application. Il fallait donc examiner les appels aux modèles. Le guide sur la latence a reproduit trois causes, chacune sur cinq exécutions :
| Cause | Ce que montre la trace | Correction | Médiane avant | Médiane après |
|---|---|---|---|---|
| Des appels indépendants envoyés l’un après l’autre | des barres en escalier | les lancer ensemble avec asyncio.gather, qui permet en Python d’attendre plusieurs appels à la fois | 3.7 s | 1.8 s |
| Du code lent entre les appels | une longue ligne your app | exécuter les quatre recherches en parallèle | 11.0 s | 9.2 s |
| Du raisonnement pour des réponses d’une ligne | un nombre de tokens de sortie bien supérieur à la réponse | reasoning_effort="none" | 25.6 s pour cinq réponses | 10.2 s |
Dans le cas du code lent, les appels aux modèles ont pris à peu près le même temps lors des deux exécutions. C’est le temps dans l’application qui a changé : dans la paire d’exécutions présentée dans le guide, la ligne your app est passée de 4.6 s à 1.3 s. Changer de modèle ne peut pas réduire cette partie. Il faut commencer par votre profileur ou vos spans OpenTelemetry. Le troisième cas utilisait GPT-6 Luna. Le paramètre de requête reasoning_effort règle la quantité de raisonnement effectuée avant la réponse. Avec la valeur none, le nombre de tokens de sortie des cinq réponses est passé de 1,150 à 190. Le guide Pourquoi mon agent LLM est-il lent ? contient le code et les traces côte à côte pour les trois cas.
Pourquoi un agent LLM coûte-t-il cher ?
Triez d’abord la liste des traces par coût, puis ouvrez la session la plus chère et consultez les colonnes du cache et des entrées, tour par tour. Le taux de succès du cache correspond à la part des tokens d’entrée lus depuis le cache de prompts du fournisseur. Un préfixe de prompt déjà vu y est facturé à une fraction du tarif d’entrée. Le guide sur les coûts a reproduit quatre causes. Les montants sont des fractions de centime, car la démonstration est petite ; ce sont donc les rapports qui sont transposables :
| Cause | Où elle apparaît | Avant | Après |
|---|---|---|---|
| Un horodatage sur la première ligne du prompt | le taux de succès du cache reste à 0% | 0% en cache | 73% en cache, coût d’entrée réduit de 66% |
| Un historique qui renvoie tous les résultats d’outils | les entrées augmentent à chaque tour | 24,546 tokens d’entrée | 8,053 tokens d’entrée |
| Une boucle de tentatives | une trace avec trop d’appels, tous au statut 200 | 10 appels | 2 appels |
| Un modèle cher pour une étape simple | le tableau des agents sur la page de session | $0.0012 | $0.0003 |
La première ligne utilise GPT-6 Luna, avec un cache initialement vide. Le guide présente une autre façon de perdre le bénéfice du cache : ajouter une instruction à chaque message utilisateur lors de l’envoi, mais pas lors de l’enregistrement. À chaque tour, le prompt reconstruit diffère alors de celui réellement envoyé au tour précédent. Enregistrer le message tel qu’il a été envoyé a fait passer le taux de succès du cache de cette conversation de 0% à 76%. Dans le cas de l’historique, tripler les tokens d’entrée n’a augmenté leur coût que de 21%, car 71% provenaient du cache. Dans la dernière ligne, trois vérifications de sous-agents sur GPT-6.1 Sol représentaient 81% du coût de la session. Les mêmes vérifications sur DeepSeek V4.1 Flash coûtaient dix fois moins. Le guide Pourquoi mon agent LLM coûte-t-il cher ? détaille chaque cas.
Comment déboguer un agent LLM à partir d’un signalement utilisateur ?
Retrouvez les exécutions de l’utilisateur grâce à un label, puis examinez la trace pour repérer l’appel défaillant, qui a souvent renvoyé un statut 200. Si chaque requête porte un label _user, la recherche metadata._user = u_1002 dans la liste des traces renvoie les sessions et les traces de cet utilisateur. Le guide de débogage a ensuite reproduit trois échecs :
- Une erreur 404 suivie d’une nouvelle tentative. Un appel à un identifiant de modèle inexistant a échoué en 95 ms pour $0. La nouvelle tentative conservait l’identifiant de trace et prenait un nouveau nom de span (
policy-check.retry). L’échec et la nouvelle tentative apparaissent donc côte à côte dans le diagramme. - Des réponses tronquées avec un statut 200. Avec
max_tokens=40et le raisonnement activé, trois appels consécutifs à DeepSeek V4.1 Flash ont renvoyé un statut 200, exactement 40 tokens de sortie et une réponse vide : le raisonnement avait épuisé le plafond. La trace montre trois fois le même span, avec un nombre de tokens de sortie égal à la limite. En portant le plafond à 2,000 et en désactivant le raisonnement, un seul appel a suffi pour obtenir une réponse de 49 tokens. Augmenter uniquement le plafond fonctionnait aussi, mais cet appel utilisait 1,876 tokens de raisonnement et prenait 22 s. - Une erreur 400 au deuxième tour. Un historique enregistré dans lequel un appel d’outil avait disparu a conduit GPT-6 Luna à rejeter le deuxième tour en 0.5 s, sans frais, alors que le premier tour avait fonctionné.
Le deuxième échec montre pourquoi chaque appel doit journaliser finish_reason, le champ de réponse qui indique pourquoi le modèle s’est arrêté. La valeur length signifie que la réponse a atteint le plafond max_tokens et doit être comptée comme un échec, même avec un statut 200. Le guide Déboguer un agent LLM énumère les cinq champs que chaque agent devrait journaliser, repris vers la fin de cet article.
Peut-on tracer un agent de programmation sans l’avoir développé ?
Claude Code peut être tracé sans modification, car il envoie déjà un identifiant de session et un identifiant d’agent avec chaque requête. Deux variables d’environnement suffisent pour le diriger vers la passerelle ; une troisième ajoute le type de chaque sous-agent :
export ANTHROPIC_BASE_URL="https://synthorai.io" # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 # optional: names sub-agent types such as Explore
Une exécution de Claude Code sur Claude Sonnet 5.5, avec deux sous-agents chargés de corriger un test en échec, a produit 11 requêtes pour $0.1728. La page de session a ventilé ce montant par agent : les deux sous-agents représentaient 39% du coût, les écritures en cache 77%, et le premier appel de chacun des trois agents 72%. La même exécution, répétée une demi-minute plus tard avec le cache chaud, a coûté $0.0812. Le guide Coût de Claude Code par sous-agent montre aussi comment répartir les dépenses d’une équipe par développeur et par dépôt.
Un modèle peut-il lire les traces à votre place ?
Un modèle peut les lire via MCP (Model Context Protocol, le protocole standard pour donner des outils à un modèle), car la passerelle expose son journal de requêtes sous forme de serveur MCP. La méthode d’accès dépend de qui pose la question :
| Qui pose la question | Solution | Tokens du modèle | Mesure |
|---|---|---|---|
| Une personne avec une question | Claude, avec la passerelle ajoutée comme connecteur | inclus dans l’abonnement de chat | 3 questions en 14 appels d’outils, tous les chiffres concordaient avec les données de facturation |
| Un programme avec une requête fixe | l’API REST | aucun | les 3 sessions les plus chères pour $0 |
| Votre propre agent, qui décide quoi examiner | des outils MCP dans le programme | à votre charge | une analyse de coût pour $0.0015 |
Dans le cas du chat, Claude Fable 5.1 s’est connecté par OAuth, une connexion dans le navigateur qui évite de coller une clé API. Il n’a reçu qu’un accès en lecture au journal de requêtes, soit quatre outils en lecture seule et une clé qui expire au bout de 7 jours. Les outils renvoient les coûts, les tokens et les statuts, sans le texte des prompts ni des réponses. Dans le cas du programme, GPT-6 Luna a repéré parmi 26 sessions celles présentant un problème de cache, de choix de modèle ou de boucle de tentatives, en un appel d’outil pour $0.0015. Claude Sonnet 5.5 a trouvé les mêmes sessions en trois appels d’outils pour $0.0631, après avoir consulté les tarifs. Les guides Interroger Claude sur vos logs LLM et Donner à votre agent des outils d’accès aux logs décrivent les deux configurations.
Que doit journaliser chaque agent ?
Cinq champs couvrent tous les problèmes de cette série :
- Un identifiant de trace par exécution et un identifiant de session par conversation, envoyés dans les en-têtes et inscrits aussi dans vos propres logs.
- Un nom de span par appel indiquant l’étape et la tentative, par exemple
plan-2oupolicy-check.retry. - Des labels pour l’utilisateur, la fonctionnalité et la version, afin de retrouver les exécutions à l’origine d’un signalement ou d’une facture.
- L’en-tête de réponse
x-request-idde chaque appel, pour retrouver ensuite son coût facturé. finish_reasonet le nombre de tokens de sortie de chaque appel, en traitantlengthcomme un échec.
Définissez aussi un délai d’expiration côté client et une valeur max_tokens pour chaque appel, tous deux assez larges. Un plafond dimensionné pour la seule réponse visible est à l’origine des réponses 200 vides décrites plus haut, et un modèle de raisonnement peut passer plusieurs minutes sur un seul appel long.
Que ne couvre pas le traçage côté passerelle ?
Le traçage à partir du journal de requêtes couvre les appels aux modèles, mais pas ce qui se passe dans votre code. La passerelle déduit le temps d’exécution de votre code à partir des intervalles entre les requêtes, comme dans le cas du code lent. Pour instrumenter vos outils, la recherche documentaire et les files d’attente avec des spans, il faut un SDK OpenTelemetry. Les deux vues peuvent partager un identifiant de trace, car la passerelle lit traceparent en l’absence de X-Trace-Id. Les traces ne contiennent pas non plus les corps des prompts et des réponses, et ne donnent le coût qu’après coup. Pour suivre un budget en temps réel, votre boucle d’agent doit calculer le coût du champ usage de chaque réponse dès sa réception.
FAQ
Qu’est-ce que l’observabilité des agents LLM ?
L’observabilité des agents LLM permet de voir ce qu’un agent a fait à chaque exécution : quels appels aux modèles il a effectués, dans quel ordre, combien de temps chacun a pris, combien chacun a coûté et lesquels ont échoué. En pratique, cela consiste à regrouper les appels en traces (une exécution) et en sessions (une conversation), avec des labels pour l’utilisateur et la fonctionnalité.
À lire aussi
- Tracer un agent LLM avec 3 en-têtes HTTP : l’agent de démonstration et les en-têtes, étape par étape.
- Référence des en-têtes de requête : tous les en-têtes de cet article, avec les alias lus depuis d’autres outils.
- Traces et sessions dans la console : la liste, le diagramme en cascade et la page de session.
- Comprendre la consommation de tokens des LLM : la signification des compteurs de tokens de chaque enregistrement, par fournisseur.
- Guide complet de la mise en cache des prompts : pourquoi un préfixe de prompt est trouvé ou non dans le cache.