Aller au contenu principal
Leçon 14 sur 25 · Construire des Agents OpenAI

Tracing et observabilité

Leçon 14 sur 25 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Tracing et observabilité
  2. Tracing intégré au SDK
  3. Nommer les traces
  4. Découper un workflow en étapes
  5. Custom trace processors
  6. Exporter vers des systèmes externes
  7. Ne pas perdre les dernières traces
  8. Désactiver le tracing
  9. Métriques clés à surveiller
  10. Points clés à retenir

Corrigé le 3 novembre 2026. Les processeurs de traces lisaient trace.start_time, trace.end_time et span.name, qui n’existent pas dans le SDK : une trace n’a pas d’horodatage, et un span porte started_at, ended_at et span_data. Le processeur d’export n’implémentait pas on_trace_start, méthode abstraite : son instanciation levait TypeError. Les « traces imbriquées » créaient une nouvelle trace dans une trace, ce que le SDK signale comme une erreur probable : les étapes passent désormais par custom_span(). Ajouts : flush_traces(), données sensibles, Zero Data Retention. Code vérifié avec openai-agents 0.24.0. Sources : Tracing (documentation du SDK) · openai-agents-python.

Tracing et observabilité

Un agent en production est une boîte noire si vous ne tracez pas ses actions. Quand un client se plaint d’une réponse absurde, vous n’avez ni la requête exacte, ni les tools appelés, ni les valeurs qu’ils ont renvoyées. Le tracing du SDK enregistre chaque étape de l’exécution : les appels au modèle, les tools utilisés, les handoffs, et les guardrails. C’est votre outil principal pour débugger, optimiser et auditer vos agents.

Tracing intégré au SDK

Bonne nouvelle : vous n’avez rien à activer. Le SDK trace automatiquement chaque exécution, et par défaut les traces sont envoyées au dashboard OpenAI sur platform.openai.com. L’agent ci-dessous, écrit sans la moindre instruction de tracing, produit déjà une trace exploitable.

from agents import Agent, Runner, function_tool

@function_tool
def rechercher_produit(nom: str) -> str:
    """Recherche un produit dans le catalogue."""
    return f"Produit trouvé : {nom}, prix 29.99€"

agent = Agent(
    name="Agent boutique",
    instructions="Vous aidez les clients à trouver des produits.",
    tools=[rechercher_produit],
    model="gpt-5.6-terra",
)

# Chaque exécution crée automatiquement une trace
result = Runner.run_sync(agent, "Cherchez le laptop pro")

Rendez-vous sur le dashboard pour voir la trace complète : durée, tokens consommés, tools appelés, et réponse finale.

Nommer les traces

Passé quelques centaines d’exécutions, le dashboard devient une liste indifférenciée. Donnez donc des noms significatifs à vos traces pour les retrouver facilement — le nom d’un parcours métier vaut mieux qu’un identifiant technique.

from agents import trace

with trace("commande-client-premium"):
    result = await Runner.run(
        agent,
        "Je veux commander 5 laptops pour mon équipe",
    )

Le nom de la trace apparaît dans le dashboard et facilite le filtrage.

Découper un workflow en étapes

Pour un workflow en plusieurs appels, ouvrez une seule trace autour de l’ensemble, puis marquez chaque étape avec custom_span(). N’imbriquez pas un trace() dans un autre : le SDK journalise alors l’avertissement Trace already exists. Creating a new trace, but this is probably a mistake. Les spans, eux, sont faits pour s’emboîter : chacun se range sous le span courant.

from agents import Runner, custom_span, trace


async def traiter_commande(message: str, conversation_id: str):
    with trace("workflow-commande", group_id=conversation_id):
        with custom_span("etape-1-comprehension"):
            result_comprehension = await Runner.run(agent_triage, message)

        with custom_span("etape-2-traitement"):
            result_traitement = await Runner.run(
                agent_commande,
                result_comprehension.to_input_list(),
            )

        with custom_span("etape-3-confirmation", data={"etape": 3}):
            result_final = await Runner.run(
                agent_confirmation,
                result_traitement.to_input_list(),
            )

    return result_final.final_output

Dans le dashboard, la trace workflow-commande montre les trois étapes, et sous chacune les spans automatiques de l’exécution : agent, appels au modèle, tools. Le group_id relie entre elles les traces d’une même conversation, par exemple avec l’identifiant de votre fil de discussion.

Par défaut, le SDK enveloppe chaque Runner.run() dans une trace nommée Agent workflow, et à l’intérieur trace l’agent, chaque génération du modèle, chaque appel de fonction, les guardrails et les handoffs. Pour une hiérarchie plus compacte, la documentation propose de couper les spans automatiques de tâche et de tour :

from agents import RunConfig

result = await Runner.run(
    agent,
    "Bonjour",
    run_config=RunConfig(tracing={"include_task_and_turn_spans": False}),
)

Custom trace processors

Le dashboard OpenAI est pratique, mais votre équipe d’exploitation surveille probablement déjà tout le reste ailleurs. Pour envoyer les traces vers votre propre système, écrivez un processeur : il reçoit les événements de début et de fin, pour la trace complète comme pour chaque span. Les six méthodes de TracingProcessor sont abstraites : implémentez-les toutes, même vides, sinon l’instanciation échoue.

Ce que chaque objet expose vraiment : une trace a un trace_id et un name, et sa méthode export() renvoie aussi group_id et metadata ; elle n’a pas d’horodatage. Un span a span_id, trace_id, parent_id, started_at et ended_at (chaînes ISO 8601), et span_data, dont l’attribut type vaut agent, generation, function, handoff, guardrail, custom… La durée d’une trace se mesure donc dans le processeur.

import time
from datetime import datetime
from typing import Any

from agents import add_trace_processor
from agents.tracing import Span, Trace, TracingProcessor


class MonProcesseurDeTraces(TracingProcessor):
    def __init__(self) -> None:
        self._debuts: dict[str, float] = {}

    def on_trace_start(self, trace: Trace) -> None:
        self._debuts[trace.trace_id] = time.monotonic()
        print(f"[TRACE START] {trace.name} - {trace.trace_id}")

    def on_trace_end(self, trace: Trace) -> None:
        duree = time.monotonic() - self._debuts.pop(trace.trace_id, time.monotonic())
        print(f"[TRACE END] {trace.name} - durée : {duree:.2f} s")

    def on_span_start(self, span: Span[Any]) -> None:
        print(f"  [SPAN START] {span.span_data.type}")

    def on_span_end(self, span: Span[Any]) -> None:
        if span.started_at and span.ended_at:
            duree = datetime.fromisoformat(span.ended_at) - datetime.fromisoformat(span.started_at)
            print(f"  [SPAN END] {span.span_data.type} - {duree.total_seconds():.2f} s")

    def shutdown(self) -> None:
        pass

    def force_flush(self) -> None:
        pass


add_trace_processor(MonProcesseurDeTraces())

add_trace_processor() ajoute votre processeur à celui d’origine : les traces continuent de partir vers le dashboard OpenAI. set_trace_processors() remplace les processeurs par défaut : rien ne part plus chez OpenAI, sauf si vous remettez vous-même son exportateur dans la liste.

Exporter vers des systèmes externes

Pour un outil d’observabilité, il suffit de reformater les spans. L’exemple ci-dessous écrit une ligne JSON par span terminé, un format que la plupart des collecteurs de journaux savent ingérer ; span.export() fournit déjà un dictionnaire complet.

import json
import logging
from typing import Any

from agents.tracing import Span, Trace, TracingProcessor

logger = logging.getLogger("agent-traces")


class ExportJsonProcessor(TracingProcessor):
    """Écrit chaque span terminé en JSON, pour un collecteur de journaux."""

    def on_trace_start(self, trace: Trace) -> None:
        pass

    def on_trace_end(self, trace: Trace) -> None:
        logger.info(json.dumps(trace.export(), default=str))

    def on_span_start(self, span: Span[Any]) -> None:
        pass

    def on_span_end(self, span: Span[Any]) -> None:
        logger.info(json.dumps(span.export(), default=str))

    def shutdown(self) -> None:
        pass

    def force_flush(self) -> None:
        pass

Attention à ce que vous exportez : par défaut, les spans de génération contiennent les entrées et sorties du modèle, et les spans de fonction les arguments et résultats des tools. Pour les écarter, passez RunConfig(trace_include_sensitive_data=False) ou définissez la variable d’environnement OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA=false. Pour des intégrations toutes faites, la documentation du SDK liste de nombreux outils tiers qui fournissent leur propre processeur.

Ne pas perdre les dernières traces

L’exportateur par défaut envoie les traces en arrière-plan, toutes les quelques secondes. Dans un worker qui traite des tâches et ne s’arrête pas, ou dans un script court, appelez flush_traces() après la fermeture de la trace pour forcer l’envoi :

from agents import flush_traces

with trace("traitement-nocturne"):
    await Runner.run(agent, "Résume les tickets du jour")
flush_traces()  # après la fin de la trace, jamais au milieu

Désactiver le tracing

Il existe un cas où le tracing gêne plus qu’il n’aide : une suite de tests qui exécute des centaines d’agents fictifs et pollue votre dashboard. Dans les tests ou en développement local, vous pouvez donc le désactiver, globalement ou pour une seule exécution.

from agents import RunConfig, set_tracing_disabled

# Désactiver complètement (ou variable d'environnement OPENAI_AGENTS_DISABLE_TRACING=1)
set_tracing_disabled(True)

# Ou pour une seule exécution
result = await Runner.run(agent, "Message de test", run_config=RunConfig(tracing_disabled=True))

Un dernier point, contractuel celui-là : d’après la documentation, le tracing vers OpenAI n’est pas disponible pour les organisations soumises à une politique Zero Data Retention. Dans ce cas, seul un processeur à vous, installé avec set_trace_processors(), vous donnera des traces.

Métriques clés à surveiller

Reste à savoir quoi regarder. En production, six indicateurs suffisent à qualifier la santé d’un agent. La latence totale — le temps entre la requête et la réponse finale — est celle que vos utilisateurs ressentent ; elle s’explique le plus souvent par le nombre de tours, c’est-à-dire le nombre de fois où la boucle agent s’est exécutée. Les tokens consommés, en entrée comme en sortie et par exécution, portent quant à eux votre facture.

Les trois autres relèvent de la qualité. Le taux d’échec des tools, soit le pourcentage de tools qui retournent une erreur, révèle une API tierce instable ou des arguments mal générés. Le taux de déclenchement des guardrails, lisible dans les spans guardrail, mesure la fréquence des blocages : une hausse brutale signale soit une attaque, soit un guardrail devenu trop strict. Enfin, la distribution des handoffs vous dit quel agent répond le plus souvent — et donc si votre triage envoie bien les demandes là où elles doivent aller.

Points clés à retenir

  • Le SDK trace automatiquement chaque exécution vers le dashboard OpenAI (sauf organisations en Zero Data Retention)
  • Une seule trace() par workflow ; les étapes se marquent avec custom_span(), et group_id relie les traces d’une conversation
  • Une trace n’a pas d’horodatage ; un span a started_at, ended_at et span_data.type
  • Un TracingProcessor implémente ses six méthodes ; add_trace_processor() ajoute, set_trace_processors() remplace
  • trace_include_sensitive_data=False écarte entrées et sorties ; flush_traces() force l’envoi
  • Désactivez le tracing dans les tests avec set_tracing_disabled(True) ou RunConfig(tracing_disabled=True)