L'OpenAI Agents SDK est la bibliothèque Python open source, sous licence MIT, qu'OpenAI met à disposition pour faire tourner des agents : un modèle, des instructions, des outils, et une boucle qui s'arrête quand l'agent a produit sa réponse finale. La version 0.24.0, publiée sur PyPI le 15 octobre 2026, s'installe en une commande et fait tourner un premier agent en une quinzaine de lignes. Les difficultés arrivent ensuite, quand l'agent doit répartir le travail entre spécialistes, se souvenir d'une conversation et laisser des traces exploitables sans exposer les données de vos clients.
Notre thèse tient en une phrase : dans l'Agents SDK, la façon dont vous reliez vos agents décide de ce que le client reçoit. Le 9 novembre 2026, nous avons envoyé la même demande double à trois architectures, avec le modèle gpt-5.4-mini. Le transfert de main entre agents, le handoff, n'a répondu qu'à la moitié de la question dans cinq essais sur cinq. Les deux autres montages ont traité les deux sujets, à des coûts très différents.
Ce tutoriel s'adresse aux développeurs et aux équipes ops qui écrivent leurs agents en Python. Chez Noxcod, la plupart de nos agents tournent sans fenêtre de chat, sur un horaire, comme les six agents qui publient chaque matin un article sur le blog de Tala. Si vous quittez Agent Builder, la correspondance nœud par nœud et l'export sont traités dans notre article sur la fermeture d'OpenAI Agent Builder. Ici, nous partons du code. Les chiffres viennent de notre essai, les règles de fonctionnement de la documentation officielle du SDK, relue le même jour.
Installer l'OpenAI Agents SDK et lancer un premier agent en Python
Il faut Python 3.10 ou plus récent, minimum exigé par la fiche du paquet sur PyPI, une clé d'API de la plateforme OpenAI et un environnement virtuel dédié, puis un pip install openai-agents. Le paquet s'importe ensuite sous le nom agents, ce qui surprend la première fois. Épinglez la version dans vos dépendances : la même fiche recense six versions publiées entre le 19 août et le 15 octobre 2026.
python3 -m venv .venv && source .venv/bin/activate
pip install "openai-agents==0.24.0"
export OPENAI_API_KEY="votre-clé"
Le premier agent de notre essai répond aux questions de facturation d'un client d'agence. Il dispose d'un seul outil, une fonction Python qui lit le statut d'une facture.
from agents import Agent, Runner, function_tool
@function_tool
def lire_facture(numero: str) -> str:
"""Renvoie le statut d'une facture client à partir de son numéro."""
return statut_depuis_la_compta(numero)
facturation = Agent(
name="Facturation",
model="gpt-5.4-mini",
instructions="Tu réponds aux questions de facturation. Utilise lire_facture.",
tools=[lire_facture],
)
result = Runner.run_sync(facturation, "Ma facture F-2041 est-elle payée ?")
print(result.final_output)
Le décorateur @function_tool transforme une fonction en outil : le SDK lit la signature et la docstring pour décrire l'outil au modèle. Runner.run_sync lance la boucle décrite dans le guide d'exécution des agents. Le modèle est appelé, les outils qu'il demande s'exécutent, leurs résultats repartent vers le modèle, et la boucle s'arrête dès qu'il rend une réponse sans appel d'outil. Dans notre essai, cette question simple a demandé deux requêtes au modèle, une pour décider d'appeler l'outil et une pour rédiger la réponse.
La boucle porte un garde-fou que peu de tutoriels mentionnent. Le paramètre max_turns vaut 10 par défaut dans le code de la version 0.24.0, et le SDK lève MaxTurnsExceeded au-delà, comme le précise la section sur les erreurs et la reprise. Pour un agent qui tourne la nuit sans surveillance, ce plafond vaut mieux qu'une boucle qui consomme des jetons jusqu'au matin, mais l'exception fait échouer le job. Le paramètre error_handlers accepte une fonction pour les clés max_turns, model_refusal et invalid_final_output, et renvoie une sortie contrôlée à la place de l'erreur. Servez-vous-en pour écrire un message lisible dans le journal du job et prévenir un humain.
Handoff ou agent comme outil : qui rédige la réponse finale
Avec un handoff, l'agent d'accueil passe la main à un spécialiste qui répond lui-même au client ; avec un agent utilisé comme outil, un agent chef garde la conversation et appelle les spécialistes comme des fonctions. La page d'orchestration de la documentation présente ces deux motifs comme les plus courants. Son critère : le handoff convient quand le spécialiste doit prendre en charge la suite du tour, l'agent outil quand il traite une sous-tâche bornée sans reprendre la conversation.
# Motif 1 : handoff, le spécialiste choisi répond seul
accueil = Agent(
name="Accueil",
instructions="Oriente la demande vers le bon spécialiste.",
handoffs=[facturation, planning],
)
# Motif 2 : agents comme outils, le chef de projet compose la réponse
chef = Agent(
name="Chef de projet",
instructions="Appelle les spécialistes nécessaires, puis rédige une réponse unique.",
tools=[
facturation.as_tool(tool_name="facturation", tool_description="Statut des factures"),
planning.as_tool(tool_name="planning", tool_description="Créneaux de point d'avancement"),
],
)
Sous le capot, les deux motifs passent par des appels d'outil. D'après la page consacrée aux handoffs, un transfert vers un agent nommé Refund Agent apparaît au modèle comme un outil appelé transfer_to_refund_agent. La différence se joue après l'appel. Après un handoff, la boucle remplace l'agent courant par le spécialiste, qui voit l'historique et produit la sortie finale. Après un appel à un agent outil, le résultat revient au chef, qui peut solliciter un second spécialiste avant de rédiger.
Ce détail pèse lourd sur les demandes qui mélangent deux sujets, car un handoff ne choisit qu'un destinataire par tour. Vous pouvez enregistrer un handoff par spécialiste et laisser le modèle choisir, mais le premier transfert met fin au rôle de l'accueil. Un client qui demande à la fois le statut d'une facture et une date de rendez-vous atterrit donc chez l'un des deux spécialistes, qui n'a pas l'outil de l'autre.
La documentation décrit une troisième voie, l'orchestration par le code. Un agent classifieur rend une sortie structurée, et votre code Python décide quels agents lancer, en parallèle si besoin avec asyncio.gather. C'est la plus prévisible des trois, et nous la recommandons dès que les catégories de demandes forment une liste fermée. Ce principe vaut pour toute l'architecture d'un agent LLM : laissez au modèle les seules décisions qu'une règle ne sait pas prendre. Quand le nombre de spécialistes grimpe, les limites des systèmes multi-agents s'appliquent aussi à l'Agents SDK.
Quel motif d'orchestration choisir dans l'Agents SDK
Selon la forme des demandes et l'agent qui doit rédiger la réponse
Un classifieur à sortie typée, puis du code qui lance les bons agents, en parallèle si besoin.
Routage par codeUn agent chef appelle chaque spécialiste nécessaire et rédige une réponse unique.
Agents comme outilsLitige ou support technique long : le transfert change d'interlocuteur et allège les instructions de l'accueil.
HandoffUne chaîne écrite dans le code, chaque sortie typée alimentant l'étape suivante.
Code, sans handoffNotre essai : une demande double envoyée à trois architectures
Dans notre essai avec l'Agents SDK et gpt-5.4-mini, sur une demande qui mêle facture et rendez-vous, le handoff a laissé le rendez-vous sans réponse cinq fois sur cinq. L'agent chef, qui appelle les spécialistes comme des outils, et le routage par code ont appelé les deux outils à chaque essai. Le montage reprend le cas d'un client d'agence qui écrit : « Ma facture F-2041 est-elle payée, et quand peut-on caler le prochain point d'avancement ? ». Deux spécialistes disposent chacun d'un outil Python, lire_facture et prochain_creneau, qui renvoient des données fictives. Nous avons lancé chaque architecture cinq fois avec gpt-5.4-mini et la version 0.24.0 du SDK, en relevant les requêtes, les jetons et la durée.
Le handoff a toujours transféré la demande au spécialiste Facturation. Celui-ci a répondu sur la facture, puis a proposé au client de « donner ses disponibilités », faute d'accès à l'agenda. C'est le montage le moins cher : 3 requêtes, 616 jetons d'entrée et 116 de sortie en médiane, pour 4,5 secondes. L'agent chef a appelé les deux spécialistes à chaque passage et rédigé une réponse complète, avec le créneau du jeudi à 14 h. Il a consommé 6 requêtes, 1 042 jetons d'entrée et 239 de sortie, et la réponse est arrivée en 6,5 secondes.
Le routage par code a classé la demande dans les deux catégories, lancé les deux spécialistes en parallèle et terminé en 4,5 secondes, avec 5 requêtes et 669 jetons d'entrée. Sa réponse brute restait pourtant impossible à montrer au client. Chaque spécialiste avait reçu la question entière, si bien que l'agent Planning écrivait « Je n'ai pas accès au statut de la facture » pendant que Facturation réclamait des disponibilités. Le code doit donc transmettre à chaque spécialiste la seule partie qui le concerne, ou ajouter une étape de rédaction finale, qui coûte une requête de plus.
Une demande double, trois architectures
Médiane de cinq essais par architecture, gpt-5.4-mini, Agents SDK 0.24.0
Mesures Noxcod du 09/11/2026, sur des outils renvoyant des données fictives.
Rapporté au tarif affiché sur la page des prix d'OpenAI le 9 novembre 2026, soit 0,75 $ par million de jetons d'entrée et 4,50 $ par million en sortie pour gpt-5.4-mini, la facture reste modeste. Pour 10 000 demandes de ce type, hors cache, comptez 9,84 $ en handoff, 11,90 $ en routage par code et 18,57 $ avec l'agent chef. L'écart pèse moins que le client qui repart avec une demi-réponse. Notre règle en découle : agent chef sur les canaux où les demandes se mélangent, routage par code partout où les catégories sont connues, handoff réservé aux spécialistes qui doivent garder la conversation. C'est la grille que notre agence spécialisée dans les agents IA applique avant d'écrire le premier agent d'un projet.
Cinq essais par architecture ne font pas une étude statistique, et vos chiffres dépendront de la longueur des instructions et des réponses d'outils. Le sens de l'écart découle en revanche du fonctionnement documenté du SDK : chaque spécialiste appelé comme outil ajoute au moins un aller-retour vers le chef, qui relit ensuite tous les résultats pour rédiger.
Sessions : donner de la mémoire à un agent sans la coder
Une session de l'Agents SDK stocke l'historique d'une conversation et le réinjecte avant chaque appel, ce qui vous évite de recoller vous-même les messages entre deux tours. La page sur les sessions décrit le mécanisme : avant chaque exécution, le runner récupère l'historique et le place devant la nouvelle entrée ; après, il enregistre tous les éléments produits, messages, appels d'outils et résultats compris.
from agents import SQLiteSession
session = SQLiteSession("client-42", "conversations.db")
Runner.run_sync(facturation, "Ma facture F-2041 est-elle payée ?", session=session)
Runner.run_sync(facturation, "Et la suivante, la F-2042 ?", session=session)
r = Runner.run_sync(facturation, "Laquelle des deux me reste à régler, et pour quand ?", session=session)
Dans notre essai, le troisième tour a donné la bonne réponse, la F-2042 à régler pour le 30 novembre, en une seule requête de 308 jetons d'entrée. Les deux statuts figuraient déjà dans l'historique, et l'agent n'a pas rappelé l'outil. Après trois tours, la session contenait 10 éléments. Nous avons ensuite posé la même troisième question sans session, six fois de suite. Quatre fois, l'agent a demandé les numéros de facture, ce qui est la bonne conduite. Deux fois, il est allé lire de lui-même la facture F-2041, dont une fois en devinant aussi la F-2042.
Ce numéro avait une origine précise : la docstring de notre outil de test citait « F-2041 » comme exemple de format. Le modèle a pris l'exemple pour une donnée. Dans un vrai projet, l'exemple d'une description d'outil peut désigner le dossier d'un autre client. Décrivez donc les formats par un motif, comme « F- suivi de quatre chiffres », et jamais avec un identifiant plausible. Ce piège ne se voit qu'en testant l'agent privé de contexte.
SQLiteSession convient au développement et aux applications simples. Pour la production, le tableau des implémentations de session oriente vers SQLAlchemySession quand une base existe déjà, PostgreSQL par exemple, et vers RedisSession quand plusieurs workers partagent la mémoire. EncryptedSession ajoute un chiffrement et une durée de vie aux éléments, par-dessus le stockage de votre choix. Une conversation longue finit par coûter cher, puisque tout l'historique repart à chaque tour. SessionSettings(limit=N) borne le nombre d'éléments relus, et OpenAIResponsesCompactionSession compacte l'historique automatiquement.
Une précaution s'impose si vous combinez sessions et agents outils. La page sur les outils précise que l'agent appelé comme outil n'hérite pas de l'état de conversation du parent. Pour que le spécialiste voie l'historique, passez-lui explicitement la même session dans as_tool. Sinon, il répond sur la seule consigne que le chef lui transmet, ce qui suffit souvent et consomme moins de jetons.
Traçage : ce que l'Agents SDK envoie à OpenAI par défaut
Le traçage de l'Agents SDK est actif par défaut et exporte vers le tableau de bord d'OpenAI chaque exécution d'agent, appel de modèle, appel d'outil, garde-fou et handoff. La page sur le traçage l'indique dans son premier encadré. Pour déboguer, ces traces sont précieuses : on y voit quel agent a pris la main, quel outil a été appelé avec quels arguments et combien de temps chaque étape a duré. Avec des données clients, il faut savoir ce qu'elles contiennent.
Le réglage trace_include_sensitive_data vaut True par défaut, selon la section sur les données sensibles. Nous l'avons vérifié avec un processeur de traces local, qui n'exportait rien. Avec le réglage par défaut, le span de l'outil lire_facture contenait l'argument {"numero": "F-2041"} et la réponse complète, « 4 800 € HT, payée le 28/10 ». Avec RunConfig(trace_include_sensitive_data=False), ces deux champs étaient vides. Les spans d'appel au modèle, eux, ne gardaient dans les deux cas que l'identifiant de la réponse et le décompte des jetons. Ce qui part le plus volontiers dans les traces, ce sont donc les entrées et sorties de vos outils, autrement dit vos données métier.
Trois réglages, décrits dans la partie sur les processeurs de traces, permettent de reprendre la main. La variable d'environnement OPENAI_AGENTS_DISABLE_TRACING=1 coupe tout. set_trace_processors() remplace l'exporteur d'OpenAI par le vôtre, alors que add_trace_processor() ajoute une destination en laissant l'export vers OpenAI actif, une nuance que la documentation souligne. Les organisations sous politique Zero Data Retention n'ont de toute façon pas accès au traçage d'OpenAI. Si vous masquez des données avant export, faites-le dans l'exporteur qui envoie. Les processeurs sont indépendants, et un processeur de masquage qui plante n'empêche pas les autres de recevoir les données brutes.
from agents import RunConfig, set_trace_processors
set_trace_processors([MonExporteur()]) # plus rien ne part vers OpenAI
config = RunConfig(trace_include_sensitive_data=False, workflow_name="support-client")
result = await Runner.run(chef, demande, run_config=config)
La liste des processeurs externes compte une trentaine d'outils d'observabilité compatibles, dont Langfuse, qui s'utilise en version hébergée ou sur vos propres serveurs selon sa page d'intégration de l'Agents SDK. Chez Noxcod, les services IA de Tala, notre produit d'accueil téléphonique, envoient leurs traces dans Langfuse, et c'est là que nous relisons les conversations qui ont mal tourné. Dans un worker Celery ou une tâche planifiée, appelez flush_traces() à la fin du job. L'exporteur par défaut envoie par lots toutes les quelques secondes, donc une trace peut n'apparaître que bien après la fin du traitement.
Mettre un agent Agents SDK en production : région, modèle et limites
En production, faites passer l'agent par le point de terminaison européen d'OpenAI si vos données doivent rester en Europe, gardez la possibilité de brancher un autre modèle, et fixez vous-même ses limites d'exécution. Pour la région, OpenAI propose un point de terminaison européen, eu.api.openai.com, avec stockage et traitement dans l'Espace économique européen et en Suisse, selon son guide sur les données de l'API. Deux conditions s'appliquent. Le projet doit relever de la surveillance des abus modifiée ou de la Zero Data Retention, et la grille tarifaire de l'API annonce une majoration de 10 % pour les modèles sortis à partir du 5 mars 2026 traités en région. Sans ces options, les journaux de surveillance des abus conservent prompts et réponses jusqu'à 30 jours par défaut.
Le choix du modèle reste ouvert. Selon la documentation des modèles, le SDK accepte tout fournisseur compatible avec l'API Chat Completions, via OpenAIChatCompletionsModel et un client pointé sur son adresse. Nous avons branché mistral-small-latest sur l'API de Mistral sans toucher aux instructions ni aux outils. Le handoff vers Facturation a fonctionné, l'outil a été appelé, et la réponse est arrivée en 2,1 secondes. Deux différences sont apparues. La réponse arrivait en Markdown, avec du gras entre astérisques, à nettoyer avant un SMS ou un e-mail en texte brut. Les outils hébergés par OpenAI, comme FileSearchTool ou WebSearchTool, ne fonctionnent qu'avec le modèle Responses d'OpenAI, donc pas sur ce chemin. Sans clé OpenAI, l'export des traces échoue aussi en erreur 401 : la documentation des modèles conseille de couper le traçage ou de brancher un autre processeur. Notre article sur l'API Mistral AI détaille la mise en place côté Mistral.
Les limites d'exécution forment la troisième décision. Fixez max_turns explicitement plutôt que de garder la valeur de 10, et branchez error_handlers pour transformer un dépassement en réponse propre. Mettez une validation humaine, needs_approval=True, sur chaque outil qui écrit dans un système réel : envoi d'e-mail, mise à jour du CRM, création de devis. Pour un agent planifié, le schéma qui tient est un worker qui charge la session depuis votre base, lance Runner.run, enregistre le résultat, puis appelle flush_traces(). Pour chiffrer le budget avant d'écrire la première ligne, notre comparatif des prix des API LLM donne les tarifs des principaux modèles.
Un agent qui touche des données clients ou un parcours de vente mérite une relecture d'architecture avant sa mise en ligne. Nous reprenons ce type de projet, du choix du motif d'orchestration au déploiement sur votre infrastructure, avec des tests sur vos demandes réelles avant la bascule : il suffit de réserver un échange avec notre équipe.
Questions fréquentes sur l'OpenAI Agents SDK
L'OpenAI Agents SDK est-il gratuit ?
La bibliothèque est gratuite et publiée sous licence MIT ; vous payez les appels au modèle. Avec gpt-5.4-mini, facturé, selon la page des tarifs de l'API OpenAI, 0,75 $ le million de jetons d'entrée et 4,50 $ en sortie au 9 novembre 2026, notre demande double coûtait entre 0,001 $ et 0,002 $ selon l'architecture. Les outils hébergés par OpenAI, comme la recherche web ou l'interpréteur de code, ont leur propre ligne sur la page des prix. Prévoyez aussi le coût de l'historique, qui repart à chaque tour d'une session.
Peut-on utiliser l'Agents SDK avec un autre modèle qu'OpenAI ?
Oui, avec tout fournisseur qui expose une API compatible Chat Completions, via OpenAIChatCompletionsModel et un client configuré sur son adresse. Nous l'avons testé avec Mistral : handoff et outils ont fonctionné sans modification. La documentation propose aussi des adaptateurs LiteLLM et Any-LLM, qu'elle présente comme des intégrations bêta. Vous perdez les outils hébergés d'OpenAI, et l'export des traces échoue sans clé OpenAI : coupez-le ou branchez votre propre exporteur.
Faut-il désactiver le traçage en production ?
Pas forcément, mais il faut décider où partent les traces et ce qu'elles contiennent. Par défaut, d'après la documentation du traçage, elles vont chez OpenAI avec les arguments et résultats de vos outils. Pour des données personnelles, passez trace_include_sensitive_data à False, ou remplacez l'exporteur par le vôtre avec set_trace_processors(). Garder des traces reste utile : sans elles, comprendre pourquoi un agent a choisi le mauvais spécialiste devient un exercice de devinette.
Comment garder la mémoire d'une conversation entre deux requêtes HTTP ?
Créez une session de l'Agents SDK identifiée par l'utilisateur ou le fil de discussion, et passez-la à chaque appel de Runner.run. En production, préférez SQLAlchemySession sur votre base PostgreSQL ou RedisSession si plusieurs workers servent le même utilisateur. Le runner relit l'historique avant l'appel et enregistre les nouveaux éléments après. Bornez la relecture avec SessionSettings(limit=N) pour qu'une longue conversation ne fasse pas grimper le coût de chaque tour.
Pourquoi mon agent s'arrête-t-il avec MaxTurnsExceeded ?
La boucle d'agent a dépassé son nombre maximal de tours, 10 par défaut, chaque tour correspondant à un appel au modèle selon le guide des exceptions. La cause habituelle tient à un outil qui renvoie une erreur ou un résultat ambigu, que le modèle rappelle en boucle. Lisez la trace pour repérer l'outil en cause, corrigez son message de retour, puis fixez max_turns selon votre cas. Le paramètre error_handlers permet de rendre une réponse propre au lieu d'une exception.