API & SDKGuide
Suivre le coût en tokens d'un agent IA en Python : usage, budget, arrêt
Journalisez le champ usage de chaque requête, additionnez-le par tâche et arrêtez l'agent à un budget en dollars. Exemple Python fonctionnel pour une API compatible OpenAI.

Sur cette page
Votre agent a tourné pendant dix minutes et la facture dépasse la tâche. Pour comprendre pourquoi, lisez l'objet usage dans chaque réponse API, additionnez-le par tâche et multipliez par le prix du modèle par million de tokens. Pour le plafonner, comparez ce total cumulé à un budget après chaque étape et arrêtez la boucle dès qu'il est dépassé. Voici une petite classe Python qui fait exactement cela.
L'exemple utilise le point de terminaison compatible OpenAI https:/ et l'identifiant de modèle deepseek- (contexte de 380K, jusqu'à 32 768 tokens de sortie par réponse). Si vous voulez estimer une exécution avant de la lancer, c'est un autre sujet, et Comment estimer le coût d'un agent IA le couvre. Cet article porte sur la mesure et le plafonnement pendant l'exécution.
Où sont les chiffres : l'objet usage#
Chaque complétion de chat non-streamée revient avec un objet usage. La référence API de DeepSeek (consultée le 26 octobre 2026) liste ces champs :
prompt_tokens— tout ce que vous avez envoyé cette fois : prompt système, historique, résultats d'outils.completion_— ce que le modèle a écrit en retour.tokens total_tokens— les deux additionnés.
DeepSeek documente aussi des extras optionnels : prompt_ et prompt_ (ils s'additionnent pour donner prompt_tokens), et completion_ pour les modèles de réflexion. La page n'affiche aucune date de publication, et ces extras sont des extensions du format OpenAI, donc le code ci-dessous les traite comme optionnels et ne s'appuie que sur les deux premiers champs.
Pourquoi journaliser chaque étape et pas seulement la dernière ? Parce qu'un agent renvoie son contexte croissant à chaque fois. L'étape 1 peut envoyer 2 000 tokens d'entrée, l'étape 30 peut en envoyer 9 000, et la facture est la somme de tous. La raison est expliquée dans pourquoi un long chat coûte plus cher, et la répartition entrée/sortie dans tokens d'entrée vs de sortie.
Un compteur qui arrête l'agent#
Voici l'ensemble : un compteur qui additionne les tokens, imprime une ligne par étape et lève une erreur quand le budget est dépassé, plus une boucle d'agent minimale autour. Installez le SDK avec pip install openai et mettez votre clé dans CHEAPTOKENS_.
import os
from openai import OpenAI
client = OpenAI(
base_url='https://api.cheaptokens.dev/v1',
api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
# example prices, USD per 1M tokens: use the current ones from the pricing table
PRICE_IN = 0.10
PRICE_OUT = 0.40
class BudgetExceeded(Exception):
pass
class TaskMeter:
def __init__(self, budget_usd):
self.budget = budget_usd
self.steps = 0
self.tokens_in = 0
self.tokens_out = 0
self.reasoning = 0
@property
def cost(self):
return (self.tokens_in * PRICE_IN + self.tokens_out * PRICE_OUT) / 1_000_000
def add(self, usage):
self.steps += 1
self.tokens_in += usage.prompt_tokens
self.tokens_out += usage.completion_tokens
details = getattr(usage, 'completion_tokens_details', None)
self.reasoning += getattr(details, 'reasoning_tokens', 0) or 0
print(f'step {self.steps}: in={usage.prompt_tokens} '
f'out={usage.completion_tokens} task=${self.cost:.4f}')
if self.cost > self.budget:
raise BudgetExceeded(
f'budget ${self.budget:.2f} crossed at step {self.steps}: '
f'${self.cost:.4f}')
def run_task(goal, budget_usd=0.50, max_steps=40):
meter = TaskMeter(budget_usd)
messages = [
{'role': 'system', 'content': 'Work step by step. Reply DONE when finished.'},
{'role': 'user', 'content': goal},
]
for _ in range(max_steps):
resp = client.chat.completions.create(
model=MODEL, messages=messages, max_tokens=1024)
meter.add(resp.usage) # log first, then decide
reply = resp.choices[0].message.content or ''
messages.append({'role': 'assistant', 'content': reply})
if 'DONE' in reply:
break
messages.append({'role': 'user', 'content': 'Continue.'})
return meter
try:
meter = run_task('Summarise the main risks of a database migration.')
print(f'finished: {meter.steps} steps, ${meter.cost:.4f}')
except BudgetExceeded as err:
print('stopped:', err)Trois choix méritent d'être copiés.
Journalisez avant de décider. meter.add enregistre d'abord l'étape et ne vérifie le budget qu'ensuite, donc une exécution arrêtée laisse une trace complète. La vérification a lieu après l'appel qui a franchi la ligne, donc vous pouvez dépasser d'au plus une étape. max_tokens=1024 plafonne la sortie de cette dernière étape.
Deux arrêts durs, pas un seul. max_steps attrape un agent qui boucle sans trop dépenser ; le budget en dollars attrape celui qui dépense beaucoup par étape. Vous voulez les deux.
Les prix vivent à un seul endroit. PRICE_IN et PRICE_OUT sont des nombres fictifs. Copiez les valeurs actuelles pour votre modèle depuis la page de tarification ; actuellement DeepSeek-V4-Flash est à $0.015 par million de tokens d'entrée et $0.10 par million de tokens de sortie.
Ce que totalise une exécution de 40 étapes#
Disons que votre agent effectue 40 étapes, et comme le contexte grandit, une étape moyenne envoie 6 000 tokens d'entrée et reçoit 400 tokens en retour. Cela fait 40 × 6 000 = 240 000 tokens d'entrée et 40 × 400 = 16 000 tokens de sortie pour la tâche.

Sur DeepSeek-V4-Flash, cela coûte $0.0052. Le tableau exécute la même charge de travail sur les autres modèles et place le prix de référence d'OpenRouter à côté du nôtre :
| Modèle | Entrée $/1M | Sortie $/1M | Coût | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.0054 | $0.0111 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0052 | $0.0139 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.0144 | $0.0912 |
240 000 tokens d'entrée et 16 000 tokens de sortie au total. Les prix par million de tokens sont actuels et évoluent avec nos tarifs ; la colonne OpenRouter indique des prix de référence pour la même charge de travail.
Au total, en comptant entrée et sortie ensemble, DeepSeek-V4-Flash revient moins cher qu'OpenRouter pour cette tâche. Remarquez aussi où sont les tokens : 240 000 des 256 000 sont en entrée. Le contexte que votre agent traîne est la facture, c'est pourquoi le nombre in= par étape dans votre journal est celui à surveiller. Un budget de 0,50 $ par tâche dans l'exemple ci-dessus est un plafond contre un emballement, pas un objectif.
Changer de modèle ne signifie que changer MODEL. Choisissez l'identifiant dans la documentation CheapTokens : les noms diffèrent entre fournisseurs, et un identifiant court tiré d'une étiquette de prix ne fonctionnera pas dans l'API. Pour les longues boucles d'agent, DeepSeek-V4-Flash a un prix unique à toute heure de la journée, donc une tâche coûte pareil à midi et à 3 h du matin. Voir la page DeepSeek pour ses limites.
Quand le modèle réfléchit#
Si une réponse est beaucoup plus longue en tokens que le texte que vous voyez, regardez reasoning_tokens. Les tokens de réflexion comptent comme sortie, donc une étape à forte composante de raisonnement peut produire bien plus de tokens de sortie que ne le suggère le texte visible, et ils sont facturés comme sortie. Le compteur ci-dessus les collecte dans meter.reasoning quand l'API renvoie le champ et le laisse à 0 sinon, donc il ne plante jamais sur une clé manquante. Imprimez-le à côté de out= si vous voulez voir quelle part de la sortie était de la réflexion. Effort de raisonnement et tokens de réflexion explique pourquoi.
Streaming : l'usage arrive en dernier#
Si votre agent streame, il n'y a pas de usage dans les chunks normaux. Dans le format OpenAI, vous le demandez avec stream_. La référence de DeepSeek (consultée le 26 octobre 2026) indique que l'option ne fonctionne qu'avec stream=True (sinon l'API répond 400), que usage vaut null dans tous les chunks sauf un, et que le dernier chunk avant data: [DONE] porte les totaux de toute la requête. Donc dans une boucle de streaming, appelez meter.add une fois, sur le chunk où usage n'est pas vide. La boucle non-streamée ci-dessus est l'endroit le plus simple pour commencer ; le streaming en Python contient la boucle elle-même.
Si les chiffres semblent faux#
usageest None. Vous streamez sansinclude_usage. Ajoutez-le, ou passez à un appel non-streamé.- Le total est inférieur à la baisse du solde. Vérifiez que chaque appel passe par le compteur : les tentatives répétées, un appel de résumé ou un appel de sélection d'outil effectué en dehors de
run_tasksont faciles à manquer. Enveloppezclient.dans une seule fonction et faites tout passer par elle.chat. completions. create - Le coût par étape ne cesse d'augmenter. C'est normal pour une boucle qui ajoute tout à
messages. Élaguez les anciennes sorties d'outils ou résumez l'historique ; sortie d'outil hors contexte montre l'idée. BudgetExceededse déclenche à l'étape 2. Votre budget est plus petit qu'une étape. Vérifiez quePRICE_INest par million de tokens, pas par millier.
Pour stocker les chiffres, écrivez meter.steps, meter.tokens_in, meter.tokens_out et meter.cost sous forme d'une ligne JSON par tâche. Une semaine de ces lignes vous donne les vrais tokens par étape pour votre agent, ce qui est le nombre à mettre dans chaque estimation par la suite. Les modèles, les prix et la première requête sont dans la documentation ; les appels d'outils sont couverts dans l'appel de fonctions en Python.



