Aller au contenu principal

Monitoring coûts et usage

Monitoring coûts et usage

Les coûts API peuvent exploser rapidement si vous ne les surveillez pas. Cette leçon vous apprend à suivre, estimer et contrôler vos dépenses en production.

Comprendre la facturation

Les coûts sont calculés par million de tokens, avec des prix différents pour l’input et l’output :

from openai import OpenAI
client = OpenAI()

# Chaque réponse inclut les métriques d'usage
response = client.responses.create(
    model="gpt-5.3",
    input="Expliquez la facturation de l'API OpenAI."
)

usage = response.usage
print(f"Input tokens : {usage.input_tokens}")
print(f"Output tokens : {usage.output_tokens}")
print(f"Total tokens : {usage.total_tokens}")

# L'output coûte plus cher que l'input !

Calculateur de coûts

from dataclasses import dataclass

@dataclass
class TarifModele:
    input_par_million: float
    output_par_million: float

# Prix approximatifs en USD par million de tokens
# Vérifiez toujours les prix actuels sur platform.openai.com/pricing
TARIFS = {
    "gpt-5.3": TarifModele(input_par_million=2.0, output_par_million=8.0),
    "gpt-5.4": TarifModele(input_par_million=10.0, output_par_million=30.0),
    "o3-pro": TarifModele(input_par_million=15.0, output_par_million=60.0),
    "o4-mini": TarifModele(input_par_million=0.5, output_par_million=2.0),
}

def calculer_cout(modele: str, input_tokens: int,
                  output_tokens: int) -> float:
    """Calcule le coût d'un appel en USD."""
    tarif = TARIFS.get(modele)
    if not tarif:
        raise ValueError(f"Modèle inconnu : {modele}")

    cout_input = (input_tokens / 1_000_000) * tarif.input_par_million
    cout_output = (output_tokens / 1_000_000) * tarif.output_par_million

    return cout_input + cout_output

# Exemple
cout = calculer_cout("gpt-5.3", input_tokens=1000, output_tokens=500)
print(f"Coût : ${cout:.6f}")
# Résultat : Coût : $0.006000

Tracker de coûts en temps réel

import json
from datetime import datetime, date
from collections import defaultdict

class CostTracker:
    """Suivi des coûts API en temps réel."""

    def __init__(self, budget_quotidien_usd: float = 10.0):
        self.budget_quotidien = budget_quotidien_usd
        self.couts_par_jour: dict[str, float] = defaultdict(float)
        self.couts_par_modele: dict[str, float] = defaultdict(float)
        self.appels_par_jour: dict[str, int] = defaultdict(int)
        self.total_tokens = 0

    def enregistrer(self, modele: str, input_tokens: int,
                    output_tokens: int):
        """Enregistre les coûts d'un appel."""
        cout = calculer_cout(modele, input_tokens, output_tokens)
        aujourdhui = date.today().isoformat()

        self.couts_par_jour[aujourdhui] += cout
        self.couts_par_modele[modele] += cout
        self.appels_par_jour[aujourdhui] += 1
        self.total_tokens += input_tokens + output_tokens

        return cout

    def budget_restant(self) -> float:
        """Retourne le budget restant pour aujourd'hui."""
        aujourdhui = date.today().isoformat()
        return self.budget_quotidien - self.couts_par_jour[aujourdhui]

    def alerte_budget(self) -> bool:
        """Vérifie si le budget est proche de la limite."""
        restant = self.budget_restant()
        if restant <= 0:
            print("ALERTE : Budget quotidien dépassé !")
            return True
        elif restant < self.budget_quotidien * 0.2:
            print(f"ATTENTION : Il reste ${restant:.2f} sur le budget quotidien")
            return True
        return False

    def rapport(self) -> dict:
        """Génère un rapport de coûts."""
        return {
            "cout_total_usd": sum(self.couts_par_jour.values()),
            "cout_aujourdhui_usd": self.couts_par_jour.get(
                date.today().isoformat(), 0
            ),
            "budget_restant_usd": self.budget_restant(),
            "couts_par_modele": dict(self.couts_par_modele),
            "total_tokens": self.total_tokens,
        }

# Utilisation
tracker = CostTracker(budget_quotidien_usd=5.0)

response = client.responses.create(
    model="gpt-5.3",
    input="Bonjour !"
)

cout = tracker.enregistrer(
    modele="gpt-5.3",
    input_tokens=response.usage.input_tokens,
    output_tokens=response.usage.output_tokens
)
print(f"Coût de cet appel : ${cout:.6f}")
print(json.dumps(tracker.rapport(), indent=2))

Client avec contrôle budgétaire

class BudgetControlledClient:
    """Client qui refuse les appels si le budget est dépassé."""

    def __init__(self, budget_quotidien: float = 10.0):
        self.client = OpenAI()
        self.tracker = CostTracker(budget_quotidien_usd=budget_quotidien)

    def create(self, model: str, input: str, **kwargs) -> object:
        """Appel API avec contrôle budgétaire."""
        # Vérifier le budget avant l'appel
        if self.tracker.budget_restant() <= 0:
            raise RuntimeError(
                "Budget quotidien épuisé. "
                f"Dépensé : ${self.tracker.rapport()['cout_aujourdhui_usd']:.2f}"
            )

        response = self.client.responses.create(
            model=model,
            input=input,
            **kwargs
        )

        cout = self.tracker.enregistrer(
            modele=model,
            input_tokens=response.usage.input_tokens,
            output_tokens=response.usage.output_tokens
        )

        # Alerte si budget faible
        self.tracker.alerte_budget()

        return response

# Utilisation
budget_client = BudgetControlledClient(budget_quotidien=2.0)

try:
    response = budget_client.create("gpt-5.3", "Bonjour !")
    print(response.output_text)
except RuntimeError as e:
    print(f"Bloqué : {e}")

Optimiser les coûts

1. Choisir le bon modèle

# Coût relatif par tâche (approximatif) :
# Classification simple : o4-mini ~$0.0005 vs GPT-5.4 ~$0.01
# Le bon modèle fait une différence de 20x sur les coûts !

def appel_optimise(prompt: str, complexite: str) -> str:
    """Choisit le modèle le plus économique adapté."""
    model = {
        "simple": "o4-mini",
        "standard": "gpt-5.3",
        "complexe": "gpt-5.4",
    }.get(complexite, "gpt-5.3")

    response = client.responses.create(model=model, input=prompt)
    return response.output_text

2. Caching des réponses

import hashlib

class CachedClient:
    """Client avec cache pour éviter les appels redondants."""

    def __init__(self):
        self.client = OpenAI()
        self.cache: dict[str, str] = {}

    def create(self, model: str, input: str, **kwargs) -> str:
        # Créer une clé de cache
        cache_key = hashlib.md5(
            f"{model}:{input}:{json.dumps(kwargs, sort_keys=True)}".encode()
        ).hexdigest()

        if cache_key in self.cache:
            print("Cache hit !")
            return self.cache[cache_key]

        response = self.client.responses.create(
            model=model, input=input, **kwargs
        )

        self.cache[cache_key] = response.output_text
        return response.output_text

3. Limiter les tokens de sortie

# Toujours fixer max_output_tokens pour les tâches prévisibles
response = client.responses.create(
    model="gpt-5.3",
    input="Classifiez ce texte : positif, négatif ou neutre.",
    max_output_tokens=10  # On attend un seul mot
)

Points clés à retenir

  • Les coûts dépendent du modèle ET de la direction (input vs output)
  • Implémentez un tracker de coûts avec alertes budgétaires
  • Utilisez le bon modèle pour chaque tâche : o4-mini pour le volume, GPT-5.3 par défaut
  • Le caching peut réduire drastiquement les coûts pour les requêtes répétitives
  • Fixez max_output_tokens quand la longueur de réponse est prévisible
  • Consultez le dashboard OpenAI pour le suivi officiel de votre consommation