Skip to main content
Pour un agent que vous avez écrit vous-même, ou un framework pour lequel Failproof AI ne dispose pas d’adaptateur. Il n’y a rien à instrumenter : vous émettez les événements. C’est la même API que les quatre adaptateurs de framework utilisent en interne. Ils ne sont que des tables de traduction par-dessus.

Installation

Aucun extra, aucune dépendance.

Instrumentation

Lisez-le de haut en bas et il dit ce qu’il signifie : Et ce que chacun émet réellement : Tout ce qui se trouve à l’intérieur peut omettre session_id et agent_id. Les portées lient l’identité sur des variables de contexte et chaque appel d’événement la récupère, vous n’avez donc jamais besoin de propager les ids à travers vos fonctions. Les trois fonctionnent avec async with comme avec with. L’imbrication d’agents construit l’arbre. parent_id et la profondeur sont calculés à partir de la pile :

Comment une portée se ferme

agent() gère les exceptions pour vous : L’erreur est émise avant agent_end, car le tableau de bord ferme le span à agent_end et tout ce qui suit ne serait attribué à rien. Une annulation n’est pas un échec, donc les exécutions annulées ne polluent pas la surface des erreurs. L’exception est toujours re-levée : une portée n’avale jamais les exceptions.

Les méthodes d’événements

Quinze méthodes en six familles. La plupart vont par paires — vous émettez l’ouverture, puis la fermeture, et le SDK mesure le span entre les deux.
Préférez les portées — agent() et tool_call() — partout où elles s’adaptent. Elles garantissent l’événement de fermeture même si le corps lève une exception. Recourez à ces méthodes directement quand votre flux de contrôle ne se prête pas à l’imbrication, comme un appel de modèle dans une fonction auxiliaire.
Les deux familles humaines pointent dans des directions opposées.Aucun framework ne signale la seconde paire, c’est donc toujours à vous de l’émettre.
Passez request_id lorsque des appels de modèles s’exécutent en parallèle. Sans lui, les requêtes et les réponses sont appariées dans l’ordre d’arrivée par agent — et les appels concurrents sont mal appariés, associant chaque réponse à la mauvaise requête.

Exemple

Une boucle d’appel d’outils contre l’API OpenAI, sans framework d’agent :
Cela produit les mêmes six types d’événements qu’un adaptateur vous fournirait. La version exécutable complète, avec les définitions d’outils, est incluse dans le dépôt du SDK sous docs/manual/examples/.

Threads et async

Les variables de contexte se propagent automatiquement dans les tâches asyncio. Elles ne se propagent pas dans les nouveaux threads, car un thread démarre avec un contexte vide.
Sans propagate(), les événements du worker lèvent une TypeError indiquant le correctif plutôt que d’atterrir sans session. C’est délibéré : un événement sans session est ignoré à l’ingestion avec une réponse 200, ce qui constitue l’échec silencieux que la couche d’identité existe précisément pour éviter.

Instrumenter un framework sans adaptateur

Tout framework d’agent vous expose les mêmes trois points d’insertion. Mappez-les et vous avez une trace complète — les quatre adaptateurs fournis ne font rien de plus que cela.
1

Encadrer l'exécution

2

Encadrer chaque outil

Dans ce que le framework appelle un wrapper d’outil ou un middleware.
3

Appairer chaque appel de modèle

Vous avez un nœud, une étape ou une frontière de middleware qui mérite d’être visible ? Enveloppez-le dans une paire de hooks — hook_triggered / hook_completed — et non dans un agent() imbriqué. agent_id est une facette à faible cardinalité, et une entrée par nœud la sature. Les spans de hooks s’affichent de la même façon et vous donnent la latence par nœud.
Manuel et automatique se combinent. Un adaptateur s’exécutant dans une portée écrite à la main rejoint cette session et se parenté à cet agent, vous obtenez donc un seul arbre plutôt que deux — utile quand vous instrumentez vous-même un framework aux côtés d’un framework supporté.
Deux raisons, et les trois points d’insertion ci-dessus sont la réponse aux deux :
  • autogen-core n’est plus maintenu depuis septembre 2025.
  • AG2 n’expose aucun point d’enregistrement global équivalent aux hooks des autres frameworks, ce qui fait qu’instrumenter AG2 implique d’envelopper chaque agent à chaque site de construction.
Mapper les points d’insertion à la main enregistre les mêmes événements, avec la même fidélité, qu’un adaptateur fourni.

Aller plus loin

Comment l’enregistrement fonctionne réellement. Rien de tout cela n’est nécessaire pour démarrer.
Chaque enregistrement a la même forme : un span s’ouvre, le travail s’imbrique à l’intérieur, et chaque événement d’ouverture reçoit un événement de fermeture correspondant.La paire est l’unité. Chaque événement de fermeture porte une durée que le SDK mesure depuis l’événement d’ouverture correspondant.Voici une vraie exécution par framework — capturée à partir des exemples fournis avec le SDK, nom de modèle normalisé. Notez tout ce qu’un seul appel renvoie.
14 events
Les nœuds deviennent des paires de hooks, vous obtenez donc la latence par nœud sans encombrer la liste des agents.
Il n’y a pas d’événement de fin de session. Une session n’est pas quelque chose que vous fermez — c’est un groupe d’événements partageant un même session_id.Le statut est dérivé de la forme de la trace :Une session se termine donc quand chaque paire est fermée. Les adaptateurs émettent agent_end pour vous, et au moment du teardown ils ferment tout ce qui est encore ouvert en le marquant incomplet — une exécution ayant planté se stabilise en done avec un écart visible plutôt que de rester suspendue.
C’est pourquoi une session peut s’étendre sur deux appels. Un interrupt() LangGraph met l’exécution en pause, le span racine reste délibérément ouvert, et l’appel de reprise le ferme. Les deux appels forment une seule session.
session_id et agent_id sont optionnels sur chaque méthode d’événement. S’ils sont omis, ils sont résolus depuis la portée englobante :
Les passer explicitement fonctionne toujours et prend la priorité. Si rien n’est lié et rien n’est passé, l’appel lève une TypeError indiquant le correctif plutôt que d’émettre un événement sans session, que l’ingestion ignorerait tout en répondant 200.Les portées lient l’identité sur des variables de contexte. Celles-ci se propagent automatiquement dans les tâches asyncio mais pas dans les nouveaux threads — enveloppez un worker dans failproofai_sdk.propagate().

Qui crée quel identifiant

Comment les adaptateurs résolvent session_id

Le premier match gagne :
  1. Une option session_id explicite
  2. Des métadonnées par appel
  3. La portée session() englobante
  4. Les métadonnées du framework
  5. Le propre id d’exécution du framework
Il n’est jamais inventé tant qu’un de ces éléments existe — un id synthétisé fractionnerait une exécution en plusieurs sessions.

Gardez agent_id à faible cardinalité

C’est la facette principale sur chaque surface du tableau de bord, et une colonne LowCardinality(String). Une valeur par exécution dégrade la colonne et remplit le menu déroulant de filtre avec une entrée par exécution.Les adaptateurs défendent cette colonne pour vous :Le vrai id est conservé dans fw_agent_id / fw_run_id, où il reste interrogeable sans être une facette.
Cette protection ne touche que les labels choisis par le framework. Un agent_id que vous passez vous-même — à event.*, ou à failproofai_sdk.agent(...) — est enregistré exactement tel quel. Réécrire silencieusement un argument explicite serait pire que la cardinalité qu’il évite, nommez donc vos propres spans en conséquence.
Quel framework enregistre quoi, mesuré depuis les exécutions ci-dessus :Un tiret signifie que le framework n’a pas ce concept. human_pause et human_interrupt décrivent une personne agissant sur l’agent, ce qu’aucun framework ne signale — émettez-les vous-même.
Un événement n’arrive jamais seul. Un ouvre un span, un le ferme, et l’événement de fermeture porte une durée que le SDK mesure depuis l’événement d’ouverture.
Un événement d’ouverture sans événement de fermeture correspondant est un span qui ne se termine jamais. La session s’affiche comme toujours en cours, indéfiniment, et sa durée active ne cesse de croître. C’est le mode d’échec à surveiller quand vous instrumentez à la main.

Règles de corrélation

  • Réutilisez le même tool_call_id, hook_id, pause_id ou input_id pour l’événement de complétion correspondant.
  • Le SDK calcule duration_ms pour tool_result, hook_completed, agent_resume et human_input. Le passer à ces méthodes lève une ValueError.
  • duration_ms est accepté sur model_response, car seul l’appelant connaît la vraie latence du fournisseur. Il doit être un entier — un flottant lève une ValueError au site d’appel, car le serveur lit la colonne comme un entier non signé 32 bits et stockerait NULL pour tout autre valeur.
  • Les clés de corrélation sont délimitées par type et session, donc un appel d’outil et un hook peuvent partager un id en toute sécurité, et deux sessions concurrentes peuvent réutiliser les mêmes ids sans collision. Elles ne sont pas délimitées par agent : une paire ouverte sous un agent et fermée sous un autre se corrèle quand même, ce qui est le cas ordinaire dans les frameworks multi-agents.
  • request_id apparie model_request avec model_response. Sans lui, les événements de modèle sont appariés dans l’ordre par agent, donc les appels concurrents sont mal appariés.
  • Une paire répartie sur plusieurs processus se corrèle toujours en aval, mais le SDK ne peut pas calculer sa durée en cours de processus.
  • La map des événements en attente contient au maximum 10 000 entrées et expulse la plus ancienne quand elle est pleine.
L’installation de failproofai-sdk installe tout, les quatre adaptateurs inclus. Les extras tirent le framework, pas l’adaptateur.
import failproofai_sdk est contractuellement sans dépendance, vérifié par un test qui installe la wheel construite avec --no-deps et un autre qui prouve qu’aucun framework n’atteint sys.modules.
Il n’existe pas d’attribut failproofai_sdk.crewai. Les adaptateurs ne sont délibérément pas exposés sur le package de premier niveau : y accéder importerait le framework comme effet de bord d’un accès d’attribut, rompant la promesse de zéro dépendance. Utilisez instrument().
La détection automatique lit sys.modules, pas la liste des packages installés, donc un framework installé mais jamais importé n’est pas instrumenté et n’est jamais importé à votre place. Pour voir ce qui est câblé :
instrument("crewai") sur une machine sans CrewAI ne lève pas d’exception. Il enregistre un avertissement et retourne (), donc un framework manquant ne fait jamais tomber un processus qui instrumente d’autres frameworks.L’avertissement porte l’ImportError sous-jacent, et ce message indique la commande d’installation exacte — le correctif est donc dans vos logs, pas caché.
Définissez FAILPROOFAI_SDK_STRICT=1 pour qu’il lève une exception à la place. Ce flag est lu une seule fois et mis en cache, exportez-le donc avant le démarrage de votre processus plutôt que de le définir en cours d’exécution.
instrument() doit venir après l’import de votre framework. La détection automatique lit sys.modules, donc un appel nu avant l’import ne trouve rien, n’installe rien et retourne ().
Faites cette erreur et le processus s’exécute avec le SDK importé, l’adaptateur apparemment installé, et pas un seul événement émis. Cela enregistre un avertissement qui le dit explicitement — vérifiez donc vos logs en premier quand une exécution n’enregistre rien.
Le spool est ce qui rend cela sûr : votre agent ne bloque jamais sur le réseau, et une panne Cloud signifie un répertoire qui grossit plutôt que des événements perdus.Chaque flush écrit un fichier batch, .tmp d’abord, puis fsync, puis un renommage atomique :
Le daemon ne prend que les .jsonl, il ne peut donc jamais lire un fichier à moitié écrit. Le nom de fichier porte un timestamp, un id de processus et un numéro de séquence, donc deux processus flushing dans la même milliseconde ne peuvent pas entrer en collision. La file d’attente est limitée à 10 000 événements ; au-delà, elle supprime les plus anciens et enregistre un log.
collector.redact ne s’applique pas à vos événements SDK. Il ne les voit jamais.
Le daemon envoie vos batches. Il ne les ouvre ni ne les réécrit.La rédaction s’exécute là où le daemon écrit ses propres événements — pas là où les batches sont envoyés. Donc un prompt ou un argument d’outil contenant une clé API la conserve à l’arrivée.C’est délibéré. Ce sont vos propres appels d’instrumentation, et réécrire les événements en transit signifierait que les événements que vous recevez ne sont pas ceux que vous avez émis.
Vous contrôlez les payloads à la source, en deux endroits :
  • Désactivez la capture de contenu sur l’adaptateur. Le nom de l’option diffère, et un adaptateur n’en a aucune — ce n’est pas un interrupteur universel unique :
    • LangChain / LangGraph, Pydantic AI — capture_content=False
    • LlamaIndex — capture_messages=False
    • CrewAI — aucun interrupteur de contenu du tout ; session_id est la seule option qu’il lit, donc les prompts et les complétions sont toujours enregistrés.
    instrument() ignore les options qu’un adaptateur ne lit pas, donc passer le mauvais nom ne lève rien et ne change rien.
  • Ne transmettez pas le secret à input= en premier lieu.
collector.redact ne remplace ni l’un ni l’autre.
Un répertoire spool vide est l’état sain. Ne l’utilisez pas pour vérifier la livraison.
Le daemon supprime chaque batch dans les millisecondes qui suivent son envoi, donc un ls est en concurrence avec le collecteur et ne montre qu’une fraction de ce que vous avez émis — impossible à distinguer d’un SDK qui n’a rien enregistré.Pour confirmer que les événements sont bien arrivés, consultez le tableau de bord. Pour observer le remplissage du spool, arrêtez d’abord le daemon.
Chaque callback s’exécute dans un wrapper dont le seul rôle est de re-lever les exceptions, votre appel se trouve donc dans exactement un try et tout ce que fait le SDK se passe en dehors.Le comportement par défaut est correct en production et problématique lors du débogage, car il ne peut que prouver que ça n’a pas planté. Définissez FAILPROOFAI_SDK_STRICT=1 pour rendre visible un échec avalé.

Problèmes courants

Un événement d’ouverture n’a pas d’événement de fermeture correspondant : un model_request sans model_response, ou un tool_use sans tool_result. Utilisez les portées, qui garantissent la paire même si le corps lève une exception. Si vous appelez les méthodes d’événements directement, utilisez try et finally.
Il est mesuré depuis l’événement d’ouverture correspondant, il est donc refusé sur tool_result, hook_completed, agent_resume et human_input. Il est accepté sur model_response, car seul vous connaissez la vraie latence du fournisseur, et il doit être un entier.
Le thread n’a jamais hérité du contexte. Enveloppez l’appelable dans failproofai_sdk.propagate(). Voir Threads et async.
Les champs supplémentaires sont fusionnés en dernier, donc un champ nommé comme un vrai champ tel que model ou outcome l’écraserait et modifierait une colonne stockée. Préfixez les vôtres ; les adaptateurs utilisent le préfixe fw_.
agent_id est une facette à faible cardinalité et vous y avez mis un id d’exécution. Utilisez un nom de rôle ou de nœud et mettez le vrai id dans un champ de payload.

Étapes suivantes

Comment ça fonctionne

Paires, ids, cycle de vie des sessions et livraison.

Lire une trace

Suivez la causalité à travers la session que vous venez de capturer.

Adaptateurs de framework

LangGraph, CrewAI, LlamaIndex et Pydantic AI.