Plus facile à lire avec un fond crème, une teinte, plus de contraste ou un texte plus grand ? Choisissez vos couleurs et votre texte, une fois pour toutes les pages.

Leçon gratuite

Tracer les appels d’IA avec des spans

Après cette leçon: Je peux tracer une requête à travers une fonction d’IA et lire la trace pour trouver ce qui était lent, cher ou en échec.

Leçon de 70 min8 commandesLab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Pourquoi cette réponse a-t-elle pris cinq secondes ?

Tracer une fonction d’IA, c’est chronométrer chaque appel de modèle et d’outil d’une requête. Un assistant de support planifie, appelle deux outils, interroge de nouveau un modèle et contrôle sa réponse. L’utilisateur attend cinq secondes, et la facture dépasse le prévu. Quelle étape était lente, et laquelle a coûté l’argent ?

Le module 69 traçait un seul appel. Cette leçon va plus loin : une exécution d’agent entière, avec les jetons, le coût et les échecs de chaque étape.

À la fin de cette leçon

Remarque

Je peux tracer une requête à travers une fonction d’IA et lire la trace pour trouver ce qui était lent, cher ou en échec.

Mots clés

MotCe que ça veut direExemple
Identifiant de spanl’identifiant d’un seul spanspan_id
Identifiant du parentl’identifiant du span qui contient celui-ciparent_span_id
Statutsi un span s’est bien terminé ou en erreurerreur
Span d’outilun span pour un outil appelé par un agent
Nombre de jetonscombien de jetons sont entrés ou sortis
Standard ouvertune conception commune que chacun peut employer

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

Un span porte identifiants, durées et faits

Chaque span a un identifiant de span et l’identifiant de trace de sa requête. Il nomme son parent par un identifiant du parent, vide pour la racine.

Il a un nom, un début, une fin et un statut : ok ou erreur. Les faits vont dans ses attributs. OpenTelemetry compte le temps en nanosecondes depuis 1970. Ce labo compte en millisecondes depuis le début, pour garder des nombres courts.

Exemple travaillé : le span d’un appel de modèle

ChampValeur
trace_id7a3f0c1d00000000000000000000aa01
span_id0000000000000005
parent_span_id0000000000000001
nommodèle : réponse
debut et fin3100 et 4900
statutok
attributsmodele m-small-2026-08, jetons_entree 2100, jetons_sortie 300, repli true

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

Ce que consigne un span de modèle

Consignez le modèle et sa version, car une nouvelle version peut changer la vitesse et le coût. Consignez les jetons d’entrée et de sortie, et la durée. Notez si l’appel a été repris ou a basculé vers un autre modèle.

Ne mettez jamais de donnée personnelle en clair sur un span. Le module 86, dans la leçon « Journaliser sobrement une application d’IA », montre comment l’écarter.

Un agent ajoute un span pour chaque outil

Un agent fait plus qu’appeler un modèle. Il planifie, appelle des outils, puis interroge de nouveau le modèle. Donnez à chaque appel d’outil son propre span d’outil, enfant du span de la requête.

Un span d’outil consigne le nom de l’outil, sa durée et son statut. Un outil en échec apparaît comme une erreur dans une requête qui peut quand même bien finir.

Écrire l’assistant du modèle, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Les jetons d’une réponse, ou null si elle n’en donne pas.' 'function compter(donnees, nom) {' '  return donnees.usage && typeof donnees.usage[nom] === \"number\" ? donnees.usage[nom] : null;' '}' > model.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire l’assistant du modèle, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function demander(messages, echantillon, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"statut \" + res.status);' '    const d = await res.json();' '    return { texte: d.choices[0].message.content, entree: compter(d, \"prompt_tokens\"), sortie: compter(d, \"completion_tokens\"), enregistre: false };' '  } catch (erreur) {' '    console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' '    return { texte: echantillon, entree: null, sortie: null, enregistre: true };' '  }' '}' 'module.exports = { demander };' >> model.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la trace de l’agent, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const TRACE = \"7a3f0c1d00000000000000000000aa01\";' 'const PRIX = { \"m-large-2026-09\": [3, 15], \"m-small-2026-08\": [1, 4] };' 'const spans = [];' 'function sid(n) { return String(n).padStart(16, \"0\"); }' 'function span(n, parent, nom, debut, fin, statut, attributs) {' '  spans.push({ trace_id: TRACE, span_id: sid(n), parent_span_id: parent ? sid(parent) : \"\",' '    nom, debut, fin, statut, attributs });' '}' > trace2.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la trace de l’agent, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'span(1, 0, \"requête de l’agent\", 0, 5000, \"ok\", {});' 'span(2, 1, \"modèle : plan\", 0, 800, \"ok\", { modele: \"m-large-2026-09\", jetons_entree: 900, jetons_sortie: 60 });' 'span(3, 1, \"outil : trouver la commande\", 800, 1100, \"ok\", { outil: \"trouver_commande\" });' 'span(4, 1, \"outil : date de livraison\", 1100, 3100, \"erreur\", { outil: \"date_livraison\" });' 'span(5, 1, \"modèle : réponse\", 3100, 4900, \"ok\", { modele: \"m-small-2026-08\", jetons_entree: 2100, jetons_sortie: 300, repli: true });' 'span(6, 1, \"contrôle de la réponse\", 4900, 5000, \"ok\", {});' 'function enfants(id) { return spans.filter(function (s) { return s.parent_span_id === id; }); }' 'function duree(s) { return s.fin - s.debut; }' >> trace2.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la trace de l’agent, partie 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function cout(a) {' '  const p = PRIX[a.modele];' '  return p ? a.jetons_entree * p[0] + a.jetons_sortie * p[1] : 0;' '}' 'function afficher(id, niveau) {' '  for (const s of enfants(id)) {' '    const marque = s.attributs.repli ? \", repli\" : \"\";' '    console.log(\"  \".repeat(niveau) + s.nom + \" \" + duree(s) + \" ms \" + s.statut + marque);' '    afficher(s.span_id, niveau + 1);' '  }' '}' >> trace2.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la trace de l’agent, partie 4

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  console.log(\"trace \" + TRACE);' '  afficher(\"\", 0);' '  const feuilles = spans.filter(function (s) { return enfants(s.span_id).length === 0; });' '  feuilles.sort(function (a, b) { return duree(b) - duree(a); });' '  console.log(\"étape la plus lente : \" + feuilles[0].nom + \", \" + duree(feuilles[0]) + \" ms\");' '  const payants = spans.filter(function (s) { return cout(s.attributs) > 0; });' '  payants.sort(function (a, b) { return cout(b.attributs) - cout(a.attributs); });' '  console.log(\"étape la plus chère : \" + payants[0].nom + \", \" + cout(payants[0].attributs) + \" unités\");' '  for (const s of spans.filter(function (un) { return un.statut === \"erreur\"; })) {' '    const haut = spans.find(function (un) { return un.span_id === s.parent_span_id; });' '    console.log(\"en échec : \" + s.nom + \", dans \" + (haut ? haut.nom : \"rien\"));' '  }' >> trace2.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la trace de l’agent, partie 5

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  const direct = { trace_id: \"7a3f0c1d00000000000000000000aa02\", span_id: sid(7), parent_span_id: \"\",' '    nom: \"modèle : contrôle en direct\", debut: 0, fin: 600, statut: \"ok\", attributs: {} };' '  const messages = [{ role: \"user\", content: \"En une phrase courte, que consigne un span ?\" }];' '  const r = await demander(messages, \"Un span consigne une étape chronométrée d’une requête.\", 40);' '  direct.attributs = { modele: \"lab\", jetons_entree: r.entree, jetons_sortie: r.sortie, enregistre: r.enregistre };' '  console.log(\"span en direct : \" + JSON.stringify(direct));' '  console.log(\"le modèle dit : \" + r.texte);' '}' 'main();' >> trace2.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Lancer et lire la trace

Commande
docker exec -w /home/node/project agent node trace2.js

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Ce que le Run affiche

LigneCe qu’elle montre
trace 7a3f0c1d00000000000000000000aa01L’identifiant de trace que partagent tous les spans
outil : date de livraison 2000 ms erreurUn span d’outil en échec
modèle : réponse 1800 ms ok, repliUn span de modèle qui a basculé vers un autre modèle
étape la plus lente : outil : date de livraison, 2000 msL’étape sans enfant la plus longue
étape la plus chère : modèle : plan, 3600 unitésLes jetons fois le prix de son modèle
span en direct : {...}Un appel réel en span. Ses jetons valent null quand un échantillon enregistré répond

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

Lire une trace de trois façons

Pour la vitesse, cherchez l’étape sans enfant qui a duré le plus. Pour le coût, multipliez les jetons de chaque span de modèle par les prix de son modèle.

Pour les échecs, listez chaque span au statut erreur et le span qui le contient. Un enfant en échec dans un span qui finit bien veut dire que l’agent s’est rattrapé.

Une erreur courante

Attention

Beaucoup pensent que l’étape la plus lente est aussi la plus chère. Ici, l’outil de date de livraison est le plus lent, mais l’appel de plan coûte le plus. L’attente fait le temps, les jetons font le coût : vérifiez les deux avant de changer quoi que ce soit.

OpenTelemetry, un standard ouvert pour les traces

OpenTelemetry est un standard ouvert pour les traces, les métriques et les journaux. Beaucoup d’outils lisent sa forme de span : un même enregistrement sert à plusieurs stockages.

Il propose aussi des noms provisoires pour les attributs des appels d’IA. Ces noms changent encore : prenez ceux du tableau comme un exemple. Vérifiez la documentation actuelle avant d’en employer un.

Noms provisoires, à vérifier dans la documentation

Notre cléUn nom provisoireCe qu’il contient
modelegen_ai.request.modelLe modèle demandé
jetons_entreegen_ai.usage.input_tokensLes jetons envoyés
jetons_sortiegen_ai.usage.output_tokensLes jetons reçus
outilgen_ai.tool.nameL’outil appelé
aucune icigen_ai.operation.nameLe genre d’appel, par exemple chat

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

À propos de ce labo et de sa source: Lab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Vous enseignez à une classe ? Proposez ce module à vos étudiants avec un guide de l'enseignant, des devoirs corrigés et un laboratoire pour chaque étudiant.