Retour au blog

Optimisation des coûtsGuide

Suivez la dépense API par projet et recevez des alertes de budget en Python

Un court script Python transforme votre journal d'utilisation en rapport de coût par projet et avertit à 50, 75, 95 et 100 % d'un budget mensuel.

Par CheapTokens TeamPublié le 6 min de lecture

Terminal avec un rapport de dépense par projet et une alerte de budget
Sur cette page
  1. Ce qu'Anthropic a livré, et ce que vous pouvez copier
  2. Le journal : une ligne par requête
  3. Le script : coût par projet et alertes de seuil
  4. Lancez-le et lisez le résultat
  5. À quoi ressemble un mois sur des modèles bon marché
  6. Où cela se passe mal

Ajoutez une ligne JSON à un fichier après chaque appel API, marquez-la avec un nom de projet, et laissez un script Python de 30 lignes transformer ce fichier en rapport de coût par projet et en alerte à 50, 75, 95 et 100 % de votre budget mensuel. C'est la même vue qu'Anthropic vient d'offrir aux équipes Claude Enterprise, construite à partir de votre propre journal.

Disons qu'un solde alimente un agent, un script de rédaction d'articles et un petit bot de support. La fin du mois arrive, le solde est plus bas que prévu, et rien ne vous dit lequel des trois en est responsable. C'est ce manque que ceci comble.

Ce qu'Anthropic a livré, et ce que vous pouvez copier#

L'annonce d'Anthropic (28 octobre 2026) couvre des rapports intelligents, des enquêtes intégrées et des budgets de groupe mutualisés, disponibles uniquement sur les offres Claude Enterprise (source). Deux parties comptent ici. Un rapport regroupe le travail d'une équipe en flux de travail et affiche la dépense de chacun. Un budget mutualisé est un nombre mensuel partagé, et les administrateurs de facturation reçoivent un e-mail à 50 %, 75 %, 95 % et 100 % de celui-ci. Anthropic ajoute que les rapports servent à planifier les investissements, pas à juger des personnes.

Vous n'avez pas besoin d'une offre entreprise pour la même forme. Un flux de travail n'est qu'un libellé que vous choisissez, et les quatre seuils sont une liste dans le code. Regroupez par projet, pas par personne, pour la raison qu'Anthropic donne.

Vérification rapide

Votre solde prépayé arrête déjà les requêtes à zéro. Pourquoi ajouter des alertes à 50 % et 75 % ?

Afficher la réponse

Réponse: Parce que zéro est trop tard : vous voulez remarquer une boucle emballée tant qu'il reste de l'argent pour enquêter

Le solde est un arrêt net, pas une alarme. Des seuils précoces transforment une surprise à 100 % en une question au neuvième jour.

Le journal : une ligne par requête#

Chaque réponse de complétion de chat porte un objet usage avec prompt_tokens et completion_tokens. C'est tout ce dont le rapport a besoin, plus un libellé de projet que vous ajoutez vous-même. Ce wrapper envoie la requête et ajoute une ligne à un fichier mensuel (le nom du fichier change chaque mois, donc un nouveau mois repart de zéro) :

python
import json, os, time
from openai import OpenAI

client = OpenAI(base_url="https://api.cheaptokens.dev/v1",
                api_key=os.environ["CHEAPTOKENS_API_KEY"])

def ask(project, model, messages):
    r = client.chat.completions.create(model=model, messages=messages)
    with open(time.strftime("usage-%Y-%m.jsonl"), "a") as f:
        f.write(json.dumps({"project": project, "model": model,
                            "prompt_tokens": r.usage.prompt_tokens,
                            "completion_tokens": r.usage.completion_tokens}) + "\n")
    return r

ask("support-bot", "zai-org/GLM-5.3-Flash",
    [{"role": "user", "content": "Where is my order?"}])

Utilisez l'identifiant de modèle complet de la documentation CheapTokens (deepseek-ai/DeepSeek-V4-Flash-0731, zai-org/GLM-5.3-Flash) ; les noms courts des tableaux de prix ne sont pas acceptés par l'API. La clé provient d'une variable d'environnement, jamais du fichier.

Flux de chaque requête vers le journal d'utilisation, le rapport par projet et les alertes
Comment une requête devient une ligne dans le journal, puis un rapport et une alerte

Le script : coût par projet et alertes de seuil#

Enregistrez ceci sous report.py. Les prix sont des espaces réservés : copiez les prix actuels depuis le tableau des tarifs (les deux modèles partagent un seul prix d'exemple ici uniquement pour garder l'arithmétique lisible).

python
import json, os, sys, time
from collections import defaultdict

LOG = time.strftime("usage-%Y-%m.jsonl")
BUDGET = float(os.environ.get("BUDGET_USD", "10"))  # example, substitute your own
STATE = LOG + ".alert"
LEVELS = [50, 75, 95, 100]
PRICES = {  # USD per 1M tokens (input, output): example, substitute current prices
    "deepseek-ai/DeepSeek-V4-Flash-0731": (1.0, 2.0),
    "zai-org/GLM-5.3-Flash": (1.0, 2.0),
}

spend = defaultdict(float)
with open(LOG) as f:
    for line in f:
        r = json.loads(line)
        p_in, p_out = PRICES[r["model"]]
        spend[r["project"]] += (r["prompt_tokens"] * p_in
                                + r["completion_tokens"] * p_out) / 1_000_000

total = sum(spend.values())
if total == 0:
    sys.exit("log is empty")

for name, usd in sorted(spend.items(), key=lambda kv: -kv[1]):
    print(f"{name:<8} ${usd:6.2f} {usd / total:5.0%}")
print(f"{'total':<8} ${total:6.2f} of ${BUDGET:.2f} ({total / BUDGET:.0%})")

last = int(open(STATE).read()) if os.path.exists(STATE) else 0
crossed = [l for l in LEVELS if total >= BUDGET * l / 100 and l > last]
if crossed:
    print(f"WARNING: budget passed {crossed[-1]}%")
    open(STATE, "w").write(str(crossed[-1]))

Les quatre dernières lignes sont la partie que les gens oublient. Sans le petit fichier d'état, le script crierait « 50 % dépassé » à chaque exécution pour le reste du mois. Avec lui, chaque niveau se déclenche une fois.

Lancez-le et lisez le résultat#

Prenez un journal jouet de quatre lignes (un vrai a une ligne par requête, donc des milliers) :

text
{"project": "agent", "model": "deepseek-ai/DeepSeek-V4-Flash-0731", "prompt_tokens": 2400000, "completion_tokens": 300000}
{"project": "agent", "model": "deepseek-ai/DeepSeek-V4-Flash-0731", "prompt_tokens": 1000000, "completion_tokens": 100000}
{"project": "blog", "model": "zai-org/GLM-5.3-Flash", "prompt_tokens": 800000, "completion_tokens": 400000}
{"project": "bot", "model": "zai-org/GLM-5.3-Flash", "prompt_tokens": 500000, "completion_tokens": 100000}

Avec les prix d'exemple et un budget de 10, python report.py affiche :

text
agent    $  4.20   65%
blog     $  1.60   25%
bot      $  0.70   11%
total    $  6.50 of $10.00 (65%)
WARNING: budget passed 50%

L'agent représente près des deux tiers de la facture, et l'alerte est arrivée à 50 %, pas à la fin. Relancez-le et l'alerte reste silencieuse jusqu'à ce que le total atteigne 75 %.

Un terminal affichant le rapport par projet et l'alerte à 50 %
Sortie du rapport sur le journal jouet : parts par projet et première alerte

Pour que cela fonctionne chaque mois, il vous faut trois étapes :

Étape par étape

Mettez-le en place en trois étapes

Journalisez chaque appel

Faites passer tous vos appels par ask(project, model, messages) et choisissez un libellé de projet court et stable. Renommer les libellés en cours de mois divise un projet en deux lignes.

Définissez les prix et le budget

Placez les prix actuels du tableau des tarifs dans PRICES, et exportez votre limite : export BUDGET_USD=25.

Exécutez-le selon un planning

Une ligne cron horaire suffit : 0 * * * * cd ~/ai && python3 report.py. Envoyez la sortie vers une commande de mail ou de chat si vous voulez que l'alerte vous soit poussée.

À quoi ressemble un mois sur des modèles bon marché#

Les chiffres deviennent utiles une fois qu'ils sont les vôtres. Supposez que l'agent du journal jouet consomme en moyenne 30 000 tokens d'entrée et 2 000 tokens de sortie par étape, 20 étapes par tâche, deux tâches par jour. Une étape sur DeepSeek-V4-Flash coûte $0.00065 ; sur GLM-5.3-Flash, $0.00068. Un jour fait 40 étapes, un mois 1 200. Déplacez les curseurs pour correspondre à votre propre agent :

Calculateur de coût

Une journée de l'agent : étapes par tâche fois tâches par jour

Tokens d'entrée par requête: 30 000 · Tokens de sortie par requête: 2 000 · Requêtes par jour: 40

ModèlePar jourPar mois
GLM-5.3-Flash$0.0272$0.816
DeepSeek-V4-Flash$0.026$0.78
MiniMax-M2.7$0.072$2.16

Et le mois à 1 200 étapes, entrée et sortie comptées ensemble :

Modèle Par requête 1 200 requêtes OpenRouter, 1 200 requêtes
GLM-5.3-Flash $0.00068 $0.816 $1.41
DeepSeek-V4-Flash $0.00065 $0.78 $2.86
MiniMax-M2.7 $0.0018 $2.16 $25.92

30 000 tokens d'entrée et 2 000 tokens de sortie par requête. 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, entrée et sortie comptées ensemble, les deux modèles reviennent moins cher que les prix de référence d'OpenRouter dans ce tableau, avec un prix à toute heure et sans surcoût de pointe. DeepSeek-V4-Flash autorise aussi jusqu'à 32 768 tokens de sortie par réponse et un contexte de 380K ; GLM-5.3-Flash a un contexte de 390K. Une fois que le rapport montre quel projet est le plus lourd, changer uniquement la chaîne model de ce projet est une modification d'une ligne, et un seul solde couvre tous les modèles. Pour savoir combien de tokens une seule requête utilise réellement, voir combien de tokens une requête API utilise ; pour arrêter une exécution d'agent en cours de route, voir suivi du coût en tokens d'un agent IA. Les prix et les modèles sont sur la page des modèles CheapTokens.

Où cela se passe mal#

  • Un modèle manque dans PRICES. Le script s'arrête avec un KeyError qui nomme le modèle. Ajoutez-le ; ne mettez pas zéro par défaut, sinon le projet semble gratuit.
  • Appels en streaming. Le dernier chunk ne porte l'utilisation que si vous la demandez (stream_options={"include_usage": True} dans le SDK OpenAI). Sinon, votre journal a des trous.
  • Requêtes échouées. Ne journalisez que les réponses qui ont un objet usage ; une requête qui a échoué n'a rien à compter.
  • Rapports par personne. Gardez le libellé sur un nom de projet ou d'agent. Un rapport sur des personnes invite à de mauvaises conclusions.