ITSkillsCenter
Intelligence Artificielle

Streaming, tokens et maîtrise des coûts avec l’API OpenAI

13 min de lecture

Deux questions finissent toujours par se poser quand un assistant passe du prototype à la vraie vie : pourquoi l’utilisateur attend-il plusieurs secondes devant un écran figé avant de voir la réponse, et combien tout cela coûte-t-il à la fin du mois ? Les deux ont une réponse technique précise. Le streaming fait apparaître la réponse mot à mot, comme une frappe au clavier, ce qui transforme l’attente perçue. Et le suivi des jetons (tokens) — l’unité de facturation — permet d’estimer et de réduire la dépense en connaissance de cause, au lieu de découvrir la note après coup.

Ce dernier volet rend notre assistant de support client agréable à utiliser et économiquement maîtrisé : réponses fluides à l’écran, coût mesuré à chaque appel, et leviers concrets pour le faire baisser sans sacrifier la qualité.

Guide principal de la série : Développer avec l’API OpenAI : modèles GPT, Responses API et bonnes pratiques.

🎯 Ce que vous allez apprendre

  • Afficher une réponse en flux continu plutôt que d’attendre la fin de la génération.
  • Lire le décompte exact de jetons consommés par un appel.
  • Estimer le nombre de jetons d’un texte avant de l’envoyer, avec tiktoken.
  • Calculer le coût en dollars d’un appel à partir de la grille tarifaire.
  • Appliquer cinq leviers concrets pour réduire la facture.

🛠️ Ce que vous allez construire

Une fonction de réponse qui affiche le texte en streaming et, une fois terminée, indique le nombre de jetons et le coût de l’appel. Vous y ajouterez un garde-fou qui mesure la taille d’une requête avant l’envoi, et une petite grille tarifaire pour comparer les modèles.

Prérequis

  • Le premier appel à l’API OpenAI en place.
  • Installer tiktoken : pip install tiktoken.
  • Avoir déjà exécuté un appel responses.create simple.
  • ⏱️ Temps estimé : environ 30 minutes.

Étape 1 — Afficher la réponse en streaming

Sans streaming, l’API génère toute la réponse puis vous la renvoie d’un bloc : l’utilisateur patiente devant un écran vide pendant toute la durée de génération. Avec stream=True, l’API émet une suite d’événements au fur et à mesure que le texte se forme. On les parcourt et on affiche chaque fragment dès son arrivée.

from openai import OpenAI

client = OpenAI()

flux = client.responses.create(
    model="gpt-5.4",
    input="Explique en trois phrases l'interet du streaming.",
    stream=True,
)

for evenement in flux:
    if evenement.type == "response.output_text.delta":
        print(evenement.delta, end="", flush=True)
print()

Chaque événement porte un type. Ceux de type response.output_text.delta contiennent un fragment de texte dans evenement.delta ; on l’affiche immédiatement avec end="" (pas de retour à la ligne) et flush=True (affichage sans attendre le tampon). Le résultat est saisissant : le texte se déroule à l’écran au rythme de sa génération. Le coût total et la durée de génération sont identiques à un appel classique — seule la perception change, mais elle change tout pour l’utilisateur.

Point d’étape — Le texte apparaît progressivement, et non d’un seul coup à la fin. Si tout s’affiche d’un bloc, vérifiez la présence de flush=True et que vous filtrez bien sur le type response.output_text.delta.

Étape 2 — Récupérer le décompte de jetons

La facturation se fait au jeton : il faut donc savoir combien chaque appel en consomme. La réponse expose un objet usage qui détaille les jetons d’entrée et de sortie. En streaming, ce décompte arrive dans l’événement final response.completed, qui porte la réponse complète.

usage = None
flux = client.responses.create(
    model="gpt-5.4",
    input="Resume la politique de retour en une phrase.",
    stream=True,
)
for evenement in flux:
    if evenement.type == "response.output_text.delta":
        print(evenement.delta, end="", flush=True)
    elif evenement.type == "response.completed":
        usage = evenement.response.usage
print()
print("Entree :", usage.input_tokens, "| Sortie :", usage.output_tokens)

On capture l’objet usage au passage de l’événement response.completed. Il contient input_tokens (votre requête, consigne système comprise), output_tokens (la réponse) et total_tokens. Il expose aussi des détails utiles, notamment les jetons mis en cache (input_tokens_details.cached_tokens), sur lesquels on revient plus bas. Ce décompte est la source de vérité : c’est exactement ce qui vous est facturé.

Étape 3 — Compter les jetons avant l’envoi

Parfois, on veut connaître la taille d’une requête avant de l’envoyer : pour refuser un texte trop long, pour découper un document, ou pour estimer un coût en amont. La bibliothèque tiktoken fait ce calcul localement, sans appel réseau. Les modèles récents (gamme GPT-5, ainsi que GPT-4o et les modèles de raisonnement) utilisent l’encodage nommé o200k_base.

import tiktoken

encodeur = tiktoken.get_encoding("o200k_base")

def compter_jetons(texte):
    return len(encodeur.encode(texte))

message = "Bonjour, ma commande 4827 n'est toujours pas arrivee, c'est urgent !"
print("Jetons estimes :", compter_jetons(message))

On charge l’encodeur une fois, puis encode convertit le texte en liste de jetons dont on prend la longueur. Le résultat est une estimation très proche de ce que facturera l’API pour la partie texte ; le décompte officiel reste celui de l’objet usage, qui inclut d’éventuels jetons techniques. Ce compteur local est précieux pour poser un garde-fou : par exemple, refuser ou tronquer une requête dépassant un certain nombre de jetons avant même de la transmettre.

Point d’étapecompter_jetons renvoie un entier cohérent (de l’ordre d’une vingtaine pour la phrase d’exemple). Une erreur sur le nom d’encodage signifie une faute de frappe : c’est bien o200k_base.

Étape 4 — Calculer le coût d’un appel

Le coût se déduit du décompte de jetons et de la grille tarifaire, exprimée en dollars par million de jetons, avec un prix d’entrée et un prix de sortie distincts. La sortie est toujours plus chère que l’entrée. Voici les tarifs des principaux modèles au moment d’écrire.

# Prix en dollars par million de jetons : (entree, sortie)
PRIX = {
    "gpt-5.5":      (5.00, 30.00),   # modele phare
    "gpt-5.4":      (2.50, 15.00),   # modele de travail
    "gpt-5.4-mini": (0.75,  4.50),   # economique
    "gpt-5.4-nano": (0.20,  1.25),   # le moins cher
}

def cout_appel(modele, usage):
    entree, sortie = PRIX[modele]
    return (usage.input_tokens / 1_000_000) * entree + \
           (usage.output_tokens / 1_000_000) * sortie

# Exemple : 800 jetons en entree, 200 en sortie, sur gpt-5.4
class U: input_tokens, output_tokens = 800, 200
print("Cout estime : $", round(cout_appel("gpt-5.4", U), 6))

La fonction multiplie chaque type de jeton par son prix unitaire ramené au jeton. Sur l’exemple — 800 jetons d’entrée et 200 de sortie avec gpt-5.4 — le coût ressort à environ 0,005 dollar, soit un demi-centime. Multiplié par des dizaines de milliers d’appels, l’ordre de grandeur devient tangible : c’est exactement ce que vous voulez chiffrer avant de lancer un service à grande échelle. Comparez la même requête sur gpt-5.4-mini : le coût est divisé par plus de trois.

Étape 5 — Cinq leviers pour réduire la facture

Une fois le coût mesuré, on agit. Cinq leviers, du plus efficace au plus fin, suffisent à maîtriser la dépense. Premier levier : le bon modèle. Une tâche simple — classer un message, extraire un champ — n’a pas besoin du modèle phare. Bascule sur gpt-5.4-mini ou gpt-5.4-nano et réserve gpt-5.5 aux raisonnements complexes ; c’est de loin l’économie la plus importante.

Deuxième levier : plafonner la sortie. La sortie coûtant le plus cher, limiter sa longueur via le paramètre de jetons maximum et une consigne explicite (« réponds en deux phrases ») réduit directement la facture. Troisième levier : le cache de préfixe. Quand plusieurs requêtes partagent un même début — typiquement une longue consigne système identique — la partie déjà vue est facturée à environ un dixième de son prix. Garder un préfixe stable et placer le contenu variable à la fin maximise ce gain ; l’objet usage vous montre la part de jetons mis en cache.

Quatrième levier : éviter l’appel inutile. Mettre en cache côté application les réponses à des questions fréquentes, ou court-circuiter le modèle quand la réponse est connue, économise des appels entiers. Cinquième levier : le traitement par lots pour les tâches non urgentes, qui bénéficie d’un tarif réduit. Combinés, ces leviers divisent souvent la facture par plusieurs, sans que l’utilisateur ne perçoive de baisse de qualité.

Comprendre la latence : premier mot et débit

La perception de rapidité repose sur deux mesures distinctes qu’il faut savoir séparer. La première est le temps jusqu’au premier mot : combien de secondes s’écoulent entre l’envoi de la requête et l’apparition du tout premier fragment de texte. C’est cette durée que le streaming attaque de front, car dès que le premier fragment arrive, l’utilisateur sait que ça travaille et son attente bascule en lecture. La seconde mesure est le débit : à quelle vitesse les mots suivants s’enchaînent une fois la génération lancée. Un débit confortable donne l’impression d’une réponse qui « coule », tandis qu’un débit lent, même avec un premier mot rapide, finit par lasser sur les réponses longues.

Ces deux mesures dépendent du modèle et de la longueur demandée. Un modèle plus léger comme gpt-5.4-mini répond souvent plus vite qu’un modèle phare, ce qui en fait un bon choix pour des interactions où la réactivité prime sur la finesse du raisonnement. De même, une consigne qui plafonne la longueur réduit mécaniquement le temps total de génération. Combiner streaming, modèle adapté et réponses concises est la recette d’un assistant qui paraît instantané, même sur une connexion modeste. À l’inverse, demander un long texte au modèle le plus lourd sans streaming produit la pire expérience possible : une attente muette de plusieurs secondes suivie d’un pavé.

Quand votre application traite plusieurs demandes en parallèle — un service web qui répond à plusieurs clients à la fois — pensez aussi à la version asynchrone du client. Elle permet de lancer plusieurs appels simultanément sans bloquer le programme, ce qui améliore nettement le débit global du service sous charge. Le principe de streaming reste identique : on itère sur les événements, mais de façon asynchrone, ce qui libère le programme pour servir d’autres requêtes pendant qu’une réponse se génère.

Suivre la consommation en production

Mesurer le coût d’un appel isolé est utile ; le suivre dans la durée est indispensable dès qu’un service tourne en continu. Le réflexe de base consiste à journaliser, pour chaque appel, le modèle utilisé, le nombre de jetons d’entrée et de sortie, et le coût calculé. Quelques lignes ajoutées à votre fonction de réponse alimentent ainsi un journal que vous pourrez agréger par jour, par fonctionnalité ou par client. Cette visibilité transforme une facture opaque en une série de chiffres actionnables : vous repérez immédiatement quelle fonctionnalité consomme le plus et où placer vos efforts d’optimisation.

Au-delà du journal applicatif, deux protections valent d’être posées dès le départ. La limite de dépense mensuelle, configurée dans le tableau de bord, agit comme un coupe-circuit : au-delà du seuil, les appels sont refusés, ce qui borne le pire scénario — une boucle accidentelle qui appellerait l’API en rafale. Les alertes de consommation, elles, vous préviennent avant d’atteindre la limite, laissant le temps de réagir. Ces garde-fous ne remplacent pas l’optimisation, mais ils éliminent la catastrophe : une erreur de code ne se traduira jamais par une facture démesurée. Pour un service amené à grandir, instaurer ce suivi dès les premiers jours évite d’avoir à le reconstituer dans l’urgence le jour où la note interroge.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
Tout le texte s’affiche d’un coup flush=True manquant ou mauvais type d’événement Filtrer sur response.output_text.delta et forcer le flush
usage reste None en streaming Événement response.completed non capturé Lire evenement.response.usage sur l’événement final
Compte tiktoken très éloigné de l’usage réel Encodage inadapté au modèle Utiliser o200k_base pour la gamme récente
Facture plus élevée que prévu Modèle phare utilisé pour des tâches simples Router les tâches simples vers mini ou nano
Aucun gain de cache observé Préfixe instable d’une requête à l’autre Figer la consigne système, mettre le variable à la fin

✅ Récapitulatif

Votre assistant affiche maintenant ses réponses en flux, ce qui supprime l’attente perçue, et chaque appel rend compte de son coût en jetons et en dollars. Vous savez estimer la taille d’une requête avant l’envoi avec tiktoken, lire le décompte officiel dans usage, et actionner cinq leviers — modèle adapté, sortie plafonnée, cache de préfixe, évitement d’appels, lots — pour garder la facture sous contrôle. C’est la dernière brique qui sépare une démonstration d’un service réellement déployable.

🧾 Aide-mémoire

Élément Rôle
stream=True Activer le flux d’événements
response.output_text.delta Fragment de texte à afficher
response.completedusage Décompte final des jetons
tiktoken.get_encoding("o200k_base") Compter les jetons localement
grille PRIX Convertir jetons en dollars
cache de préfixe Réduire le coût d’un préfixe répété

💪 À vous de jouer

Écrivez une fonction repondre_economique(question) qui choisit automatiquement le modèle : gpt-5.4-nano si la question fait moins de 30 jetons (probablement simple), gpt-5.4 sinon. Affichez le modèle retenu et le coût.

Voir une piste
def repondre_economique(question):
    modele = "gpt-5.4-nano" if compter_jetons(question) < 30 else "gpt-5.4"
    r = client.responses.create(model=modele,
        input=[{"role": "user", "content": question}])
    print("Modele :", modele, "| Cout : $", round(cout_appel(modele, r.usage), 6))
    return r.output_text

Tutoriels associés

FAQ

Le streaming coûte-t-il plus cher ?
Non. Le coût dépend du nombre de jetons, pas du mode de réception. Le streaming change seulement la façon dont la réponse vous parvient, pas son prix.

tiktoken donne-t-il le chiffre exact facturé ?
C'est une estimation très proche pour le texte. Le décompte facturé fait foi via l'objet usage, qui peut inclure quelques jetons techniques supplémentaires.

Qu'est-ce qu'un jeton, concrètement ?
Un fragment de mot. En moyenne, un jeton vaut environ trois quarts d'un mot en anglais, un peu moins en français où les mots sont parfois découpés en plusieurs jetons.

Comment éviter une mauvaise surprise sur la facture ?
Définissez une limite de dépense mensuelle dans le tableau de bord et journalisez le coût de chaque appel avec la fonction de l'étape 4 pour suivre la consommation en temps réel.

Partager
Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité