Tracing et observabilité
Sur cette page
Corrigé le 3 novembre 2026. Les processeurs de traces lisaient
trace.start_time,trace.end_timeetspan.name, qui n’existent pas dans le SDK : une trace n’a pas d’horodatage, et un span portestarted_at,ended_atetspan_data. Le processeur d’export n’implémentait pason_trace_start, méthode abstraite : son instanciation levaitTypeError. 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 parcustom_span(). Ajouts :flush_traces(), données sensibles, Zero Data Retention. Code vérifié avecopenai-agents0.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 aveccustom_span(), etgroup_idrelie les traces d’une conversation - Une trace n’a pas d’horodatage ; un span a
started_at,ended_atetspan_data.type - Un
TracingProcessorimplé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)ouRunConfig(tracing_disabled=True)