Leçon 7 — Mesurer et livrer

Suivre et prévoir les coûts

6 min de lecture

Suivre et prévoir les coûts

Ce qui est facturé

Les modèles Gemini sont facturés au jeton (token), un morceau de mot : en ordre de grandeur, un mot français fait un à deux jetons. On paie séparément :

  • les jetons d'entrée : instruction système + prompt (ici, les avis) ;
  • les jetons de sortie : la réponse, plus les jetons de réflexion (thinking) des modèles qui réfléchissent avant de répondre.

Les prix (en dollars par million de jetons) dépendent du modèle et du mode : la page Tarifs indique notamment que le mode lot coûte la moitié du tarif standard.

flowchart LR
  R["Réponse de Gemini"] --> U["usage_metadata"]
  U --> P["prompt_token_count"]
  U --> C["candidates_token_count"]
  U --> T["thoughts_token_count"]
  P --> J["resultats.jsonl : tokens_in"]
  C --> K["tokens_out = réponse + réflexion"]
  T --> K
  J --> RP["rapport.py : coût = in x P_in + out x P_out"]
  K --> RP

Enregistrer les jetons avec chaque lot

files/classer.py
u = r.usage_metadata
return {"resultats": valider(avis, r.text), "tokens_in": u.prompt_token_count or 0,
        "tokens_out": (u.candidates_token_count or 0) + (u.thoughts_token_count or 0)}

Les jetons sont écrits dans la même ligne que les résultats du lot : le point de reprise est aussi le registre des coûts. Un lot refait après une coupure n'est compté qu'une fois dans le fichier... mais il a été payé deux fois chez le fournisseur ; c'est le coût d'une coupure, borné par la concurrence.

Calculer le coût

files/rapport.py
prix_in = float(os.environ.get("PRICE_IN_PER_M", "0.10"))
prix_out = float(os.environ.get("PRICE_OUT_PER_M", "0.40"))
cout = t_in / 1e6 * prix_in + t_out / 1e6 * prix_out

Les prix viennent de l'environnement (fichier .env.example) : les valeurs par défaut sont des exemples. Recopiez celles de la page Tarifs pour votre modèle, le jour où vous faites le calcul : les prix changent.

couˆt=jetons entreˊe×Pin+jetons sortie×Pout106\text{coût} = \frac{\text{jetons entrée} \times P_{in} + \text{jetons sortie} \times P_{out}}{10^6}

Prévoir : extrapoler depuis un échantillon

On ne lance pas 100 000 documents « pour voir ». On mesure sur un petit lot représentatif, puis on extrapole :

Mesure sur 200 avis (2026-10-15)Valeur
jetons d'entrée6 884, soit environ 34 par avis
jetons de sortie (réflexion comprise)9 468, soit environ 47 par avis
requêtes20 (lots de 10)

Pour 100 000 avis semblables : environ 3,4 millions de jetons d'entrée et 4,7 millions de sortie. Avec les prix d'exemple du .env.example (0,10 et0,40et 0,40 par million) : 3,4×0,10+4,7×0,40≈2,23{,}4 \times 0{,}10 + 4{,}7 \times 0{,}40 \approx 2{,}2 $ en synchrone, environ la moitié en mode lot. Refaites ce calcul avec les vrais prix de votre modèle.

LevierEffet sur le coût
mode lot (Batch API, Vertex AI batch)environ moitié prix, si le délai est acceptable
modèle plus léger (variante lite)souvent bien moins cher : à valider sur la qualité (leçon 4.2)
regrouper par lotsinstruction système payée une fois par lot
limiter la réflexion (types.ThinkingConfig)moins de jetons de sortie sur une tâche simple de classement
sortie courte et structuréepas de phrases inutiles dans la réponse

Sur un essai de 200 documents, vous mesurez 9 468 jetons de sortie. Combien en prévoir pour 50 000 documents semblables ?

Quels éléments font partie des jetons de sortie facturés ? (plusieurs réponses)

Exercice : un garde-fou de budget

Ajoutez à traiter() un paramètre budget_jetons : dès que la somme des jetons (entrée + sortie) des lots terminés le dépasse, plus aucun nouveau lot ne démarre.

Voir la solution
classer.py
async def traiter(client, avis, sortie, taille_lot=10, concurrence=4, rpm=60,
                  traiter_lot=classer_lot, budget_jetons=None) -> dict:
    ...
    stats = {"lots_ok": 0, "lots_echec": 0, "avis_ok": 0, "jetons": 0}

    async def un_lot(lot):
        async with sem:
            if budget_jetons and stats["jetons"] >= budget_jetons:
                return                                   # budget atteint : on ne démarre plus rien
            await debit.attendre()
            res = await avec_reessais(lambda: traiter_lot(client, lot))
            ecrire_lot(sortie, {"lot": [a["id"] for a in lot], "modele": MODEL, **res})
            stats["jetons"] += res["tokens_in"] + res["tokens_out"]
            ...

Le test se vérifie à l'entrée du sémaphore : les lots déjà en vol se terminent (au plus concurrence lots au-delà du budget). Les lots non démarrés n'ont pas de ligne dans le point de reprise : ils seront traités à la prochaine exécution, avec un nouveau budget.

Résultat obtenu

Sortie réelle de rapport.py sur les 200 avis classés le 2026-10-15 (gemini-3.5-flash-lite, prix d'exemple du .env.example) :

Jetons : 6884 en entrée, 9468 en sortie ; coût estimé au tarif standard 0.0045 $ (au tarif Batch, moitié prix : 0.0022 $)

Le même jour, le lot de 20 avis sur Vertex AI (gemini-2.5-flash) a consommé 822 jetons d'entrée et 3 066 de sortie, soit environ 153 jetons de sortie par avis contre environ 47 ici : la différence vient surtout de la réflexion du modèle.

Je sais faire

  • Lire usage_metadata et compter aussi les jetons de réflexion
  • Enregistrer les jetons avec chaque lot dans le point de reprise
  • Calculer un coût à partir de prix configurables, sans les coder en dur
  • Extrapoler un coût depuis un échantillon et poser un garde-fou de budget
Suivant