Aller au contenu principal

Comptage de tokens et budgétisation

Mis à jour le 28 juillet 2026

Comprendre les tokens

Les tokens sont l’unité fondamentale de mesure pour les LLM. Chaque appel API est facturé en tokens, et chaque modèle refuse les requêtes qui dépassent sa fenêtre de contexte. Maîtriser le comptage de tokens vous permet donc de faire trois choses que l’approximation ne permet pas : prédire les coûts avant de les subir, optimiser vos prompts sur des chiffres plutôt que sur une impression, et respecter les limites de contexte sans découvrir le dépassement au moment de l’erreur API.

Compter avec tiktoken

La bibliothèque tiktoken applique exactement le même découpage que le modèle. Vous instanciez un encodeur pour le modèle visé, puis vous comptez la longueur de la séquence encodée.

import tiktoken

# Encoder pour les modèles GPT
encodeur = tiktoken.encoding_for_model("gpt-5.6-terra")

def compter_tokens(texte: str) -> int:
    """Compte le nombre de tokens dans un texte."""
    return len(encodeur.encode(texte))

# Exemples
print(compter_tokens("Bonjour"))           # ~1-2 tokens
print(compter_tokens("Bonjour le monde"))  # ~3-4 tokens
print(compter_tokens("Développement"))      # ~2-3 tokens (les accents coûtent plus)

Le troisième exemple mérite un arrêt. « Développement » coûte plus cher que sa longueur en caractères ne le laisserait croire, parce que les caractères accentués se découpent moins bien. Concrètement, un corpus français consomme davantage de tokens qu’un corpus anglais équivalent : si vous avez estimé votre budget sur des prompts en anglais et que vous déployez en français, votre facture réelle dépassera votre prévision.

Un appel API ne transporte pas qu’un texte brut : chaque message porte un rôle, parfois un nom, et l’API ajoute des jetons de structure. La fonction suivante intègre cet overhead pour donner un total réaliste.

def compter_tokens_conversation(messages: list[dict]) -> int:
    """Compte les tokens d'une conversation (messages + overhead)."""
    total = 0
    for message in messages:
        total += 4  # Overhead par message (role, etc.)
        total += compter_tokens(message["content"])
        if message.get("name"):
            total += compter_tokens(message["name"])
    total += 2  # Tokens de fin
    return total

conversation = [
    {"role": "system", "content": "Vous êtes un assistant technique."},
    {"role": "user", "content": "Expliquez les microservices."},
    {"role": "assistant", "content": "Les microservices sont une architecture..."},
]

print(f"Tokens totaux : {compter_tokens_conversation(conversation)}")

Sur une conversation de trois messages, l’overhead est négligeable. Sur un historique de deux cents messages courts — typique d’un chat de support —, il représente près d’un millier de tokens que vous n’aviez pas comptés.

Budgétiser avant d’appeler

La fenêtre de contexte n’est pas un budget d’entrée : c’est un budget partagé entre ce que vous envoyez et ce que le modèle produit. Si vous remplissez la fenêtre à ras bord avec votre prompt, la réponse sera tronquée. La structure BudgetTokens rend cette répartition explicite en réservant d’emblée une part pour la sortie.

from dataclasses import dataclass

@dataclass
class BudgetTokens:
    """Gère le budget de tokens pour un appel."""
    limite_contexte: int       # Fenêtre du modèle
    tokens_systeme: int = 0    # Prompt système
    tokens_historique: int = 0 # Historique conversation
    tokens_utilisateur: int = 0 # Message en cours
    reserve_sortie: int = 2000 # Réservé pour la réponse

    @property
    def tokens_disponibles(self) -> int:
        utilises = (
            self.tokens_systeme
            + self.tokens_historique
            + self.tokens_utilisateur
        )
        return self.limite_contexte - utilises - self.reserve_sortie

    @property
    def depasse(self) -> bool:
        return self.tokens_disponibles < 0

    def rapport(self) -> str:
        return (
            f"Contexte: {self.limite_contexte} | "
            f"Système: {self.tokens_systeme} | "
            f"Historique: {self.tokens_historique} | "
            f"Utilisateur: {self.tokens_utilisateur} | "
            f"Réserve sortie: {self.reserve_sortie} | "
            f"Disponible: {self.tokens_disponibles}"
        )

# Exemple avec une fenêtre de contexte de 128K tokens
budget = BudgetTokens(
    limite_contexte=128_000,
    tokens_systeme=compter_tokens(prompt_systeme),
    tokens_historique=compter_tokens_conversation(historique),
    tokens_utilisateur=compter_tokens(message_utilisateur),
)

if budget.depasse:
    print("Budget dépassé — compaction nécessaire")

La méthode rapport sert davantage qu’à déboguer. Loguez-la sur un échantillon de requêtes de production et vous verrez immédiatement quel poste consomme votre contexte. Dans la plupart des applications, la surprise vient de l’historique, rarement du prompt système que tout le monde surveille.

Reste à agir quand le budget est dépassé. La fonction preparer_contexte applique la règle la plus simple et la plus robuste : on garde les messages les plus récents et on abandonne les plus anciens dès que le budget est épuisé. Le parcours se fait en sens inverse, avec une insertion en tête pour rétablir l’ordre chronologique.

def preparer_contexte(
    prompt_systeme: str,
    historique: list[dict],
    message: str,
    limite_modele: int = 128_000,
    reserve_sortie: int = 4_000,
) -> list[dict]:
    """Prépare le contexte en respectant le budget de tokens."""

    tokens_systeme = compter_tokens(prompt_systeme)
    tokens_message = compter_tokens(message)
    budget_historique = (
        limite_modele - tokens_systeme - tokens_message - reserve_sortie
    )

    # Garder les messages les plus récents dans le budget
    messages_gardes = []
    tokens_utilises = 0

    for msg in reversed(historique):
        tokens_msg = compter_tokens(msg["content"]) + 4
        if tokens_utilises + tokens_msg > budget_historique:
            break
        messages_gardes.insert(0, msg)
        tokens_utilises += tokens_msg

    return (
        [{"role": "system", "content": prompt_systeme}]
        + messages_gardes
        + [{"role": "user", "content": message}]
    )

Cette troncature est brutale : ce qui sort du budget est perdu. Quand le contexte ancien compte vraiment — un dossier client suivi sur plusieurs semaines —, remplacez l’abandon pur et simple par la compaction vue à la leçon précédente.

Estimer le coût avant d’engager la dépense

Les erreurs de facturation les plus coûteuses viennent des traitements par lot : vous lancez une passe sur 10 000 documents et vous découvrez le montant le lendemain. Estimer d’abord, en quelques millisecondes de calcul local, vous évite ce scénario.

def estimer_cout(
    tokens_entree: int,
    tokens_sortie_estime: int,
    modele: str = "gpt-5.6-terra",
) -> dict:
    """Estime le coût d'un appel avant de l'exécuter."""
    # Tarifs par million de tokens (juillet 2026 — vérifiez la grille officielle)
    tarifs = {
        "gpt-5.6-luna": {"input": 0.20, "output": 1.20},
        "gpt-5.6-terra": {"input": 2.00, "output": 12.00},
        "gpt-5.6-sol": {"input": 5.00, "output": 30.00},
    }

    tarif = tarifs.get(modele, tarifs["gpt-5.6-terra"])
    cout_entree = (tokens_entree / 1_000_000) * tarif["input"]
    cout_sortie = (tokens_sortie_estime / 1_000_000) * tarif["output"]

    return {
        "modele": modele,
        "tokens_entree": tokens_entree,
        "tokens_sortie_estime": tokens_sortie_estime,
        "cout_entree": f"${cout_entree:.6f}",
        "cout_sortie": f"${cout_sortie:.6f}",
        "cout_total": f"${cout_entree + cout_sortie:.6f}",
    }

# Avant d'envoyer un gros prompt
estimation = estimer_cout(
    tokens_entree=50_000,
    tokens_sortie_estime=2_000,
    modele="gpt-5.6-sol",
)
print(estimation)

La table de tarifs est codée en dur dans cet exemple, avec la date en commentaire. Traitez-la comme une donnée de configuration à vérifier régulièrement contre la grille officielle : une estimation calculée sur des prix périmés est plus dangereuse qu’une absence d’estimation, parce qu’elle inspire confiance.

Enfin, connaissez les limites de chaque modèle pour planifier votre budget. Les fenêtres de contexte varient d’un modèle à l’autre au sein de la famille GPT-5.6 — consultez la page officielle des modèles OpenAI pour les valeurs à jour, et paramétrez limite_contexte depuis votre configuration plutôt qu’en dur dans le code appelant.

Points clés à retenir

  • Utilisez tiktoken pour compter les tokens avant chaque appel, en tenant compte de l’overhead par message
  • Le français consomme plus de tokens que l’anglais à contenu équivalent : ajustez vos estimations
  • Réservez toujours un budget pour les tokens de sortie dans la fenêtre de contexte
  • Gérez le contexte automatiquement en éliminant les messages les plus anciens
  • Estimez les coûts avant d’envoyer les requêtes coûteuses, sur des tarifs vérifiés