Utiliser les API#

Les chapitres précédents ont présenté le fonctionnement interne des LLM : l’architecture Transformer, la tokenisation, les stratégies de décodage. Mais en pratique, la majorité des développeurs n’entrainent pas leurs propres modèles — ils les consomment via des API (Application Programming Interfaces). L’API est le point de contact entre un modèle hébergé par un fournisseur (Anthropic, OpenAI, Google, Mistral) et l’application qui l’utilise. Comprendre la structure de ces API, leurs paramètres, leurs coûts et leurs contraintes est indispensable pour construire des systèmes fiables.

Ce chapitre est résolument pratique. Nous examinerons d’abord les arbitrages entre modèles locaux et API distantes, puis nous détaillerons la structure d’un appel API (messages, roles, paramètres). Nous comparerons ensuite les API d’Anthropic (Claude) et d’OpenAI (GPT), avant d’aborder le streaming, la gestion de la fenêtre de contexte, et l’optimisation des coûts.

Tous les exemples d’appels API sont présentés sous forme de blocs de code non exécutables : ils nécessitent des clés d’API qui ne sont pas disponibles dans cet environnement de build. Les cellules exécutables sont réservées aux calculs locaux — comptage de tokens, estimations de coûts, visualisations.

Pourquoi les API ?#

Un LLM peut être deployé de deux facons : localement (le modèle tourne sur votre propre infrastructure) ou via une API distante (le modèle tourne sur les serveurs du fournisseur). Chaque approche présente des compromis distincts.

Définition 20 (API de LLM)

Une API de LLM est une interface de programmation qui permet d’envoyer des requêtes textuelles (prompts) à un modèle de langage hébergé à distance et de recevoir des réponses générées. L’interaction se fait généralement par requêtes HTTP (REST) ou via des bibliothèques clientes officielles. L’utilisateur paie à l’usage (par token) sans gérer l’infrastructure sous-jacente.

Les principaux critères de choix entre une approche locale et une approche API sont les suivants :

Critère

Modèle local

API distante

Coût initial

Elevé (GPU, infrastructure)

Nul (pay-per-use)

Coût marginal

Faible (électricité, maintenance)

Proportionnel au volume

Latence

Variable (dépend du hardware)

Faible et stable

Capacités

Limitées par le modèle choisi

Accès aux meilleurs modèles

Confidentialité

Données restent en local

Données transitent par le fournisseur

Maintenance

A votre charge

Gérée par le fournisseur

Personnalisation

Fine-tuning complet possible

Fine-tuning limité ou absent

Remarque 19

Le choix entre local et API n’est pas binaire. De nombreux systèmes de production combinent les deux : un modèle local léger pour les tâches simples et peu sensibles (classification, extraction), et une API vers un modèle frontier pour les tâches complexes (raisonnement, génération longue). Cette architecture hybride optimise le rapport coût/performance tout en préservant la confidentialité là où c’est nécessaire.

Remarque 20

Les clés d’API sont des secrets d’authentification qui donnent accès à des ressources facturées. Elles ne doivent jamais être écrites en dur dans le code source ni commitées dans un dépôt Git. Les bonnes pratiques incluent : variables d’environnement (ANTHROPIC_API_KEY), fichiers .env exclus du versioning, ou gestionnaires de secrets (AWS Secrets Manager, HashiCorp Vault). Une clé exposée peut entrainer des coûts non autorisés et des fuites de données.

Structure d’un appel API#

Malgré les différences entre fournisseurs, tous les appels API de LLM partagent une structure commune : un format de messages organisé par rôles, un choix de modèle, et un ensemble de paramètres de génération.

Le format de messages#

Les API modernes utilisent un format conversationnel structuré en messages. Chaque message a un rôle et un contenu.

Définition 21 (System prompt)

Le system prompt (ou message système) est une instruction de haut niveau envoyée au modèe avant la conversation. Il définit le comportement, le ton, les contraintes et le rôle du modèle. Il n’est pas visible pour l’utilisateur final dans les interfaces conversationnelles, mais il influence toute la génération subséquente. Le system prompt est le principal levier de cadrage d’un LLM via API.

Les trois rôles standard sont :

  • system : instructions globales qui cadrent le comportement du modèle (ton, expertise, contraintes, format de sortie).

  • user : messages de l’utilisateur (questions, demandes, documents à analyser).

  • assistant : réponses du modèle, ou réponses partielles pré-remplies pour guider la génération (prefilling).

Hide code cell source

# Import des librairies Python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import json
import tiktoken

Hide code cell source

# Structure d'une conversation typique en format messages
conversation = [
    {
        "role": "system",
        "content": "Tu es un assistant expert en Python. Reponds de façon concise."
    },
    {
        "role": "user",
        "content": "Comment inverser une liste en Python ?"
    },
    {
        "role": "assistant",
        "content": "Trois méthodes principales :\n1. `liste[::-1]` (copie inversée)\n2. `list(reversed(liste))` (iterateur inverse)\n3. `liste.reverse()` (inversion in-place)"
    },
    {
        "role": "user",
        "content": "Quelle est la plus rapide ?"
    }
]

print(json.dumps(conversation, indent=2, ensure_ascii=False))
[
  {
    "role": "system",
    "content": "Tu es un assistant expert en Python. Reponds de façon concise."
  },
  {
    "role": "user",
    "content": "Comment inverser une liste en Python ?"
  },
  {
    "role": "assistant",
    "content": "Trois méthodes principales :\n1. `liste[::-1]` (copie inversée)\n2. `list(reversed(liste))` (iterateur inverse)\n3. `liste.reverse()` (inversion in-place)"
  },
  {
    "role": "user",
    "content": "Quelle est la plus rapide ?"
  }
]

Hide code cell source

# Visualisation schématique de la structure des messages
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.axis('off')

roles = [
    ("system", "Tu es un assistant expert en Python...", "#4C72B0", 8.5),
    ("user", "Comment inverser une liste ?", "#DD8452", 6.5),
    ("assistant", "Trois méthodes : [::-1], reversed()...", "#55A868", 4.5),
    ("user", "Quelle est la plus rapide ?", "#DD8452", 2.5),
    ("assistant", "→  génération par le modèle", "#C44E52", 0.5),
]

for role, text, color, y in roles:
    rect = mpatches.FancyBboxPatch((0.5, y), 9, 1.4, boxstyle="round,pad=0.15",
                                    facecolor=color, alpha=0.25, edgecolor=color, linewidth=2)
    ax.add_patch(rect)
    ax.text(1.0, y + 0.95, role.upper(), fontsize=11, fontweight='bold', color=color, va='center')
    ax.text(1.0, y + 0.35, text, fontsize=9, color='#333333', va='center')

# Fleche indiquant le flux
for i in range(len(roles) - 1):
    y_start = roles[i][3]
    y_end = roles[i + 1][3] + 1.4
    ax.annotate('', xy=(5, y_end), xytext=(5, y_start),
                arrowprops=dict(arrowstyle='->', color='gray', lw=1.5))

ax.set_title("Structure d'une conversation API (messages et rôles)", fontsize=13, pad=15)
plt.show()
_images/3f5dfc7faaed21de8186747c77b8a05986b6c5744e194eecde7b60478ddf4088.png

Paramètres de génération#

Les paramètres principaux contrôlent le comportement du modèle :

Paramètre

Description

Valeurs typiques

model

Identifiant du modèle

claude-sonnet-4-20250514, gpt-4o

max_tokens

Nombre maximal de tokens générés

256 – 4096

temperature

Contrôle l’aléatoire (0 = déterministe)

0.0 – 1.0

top_p

Echantillonnage par noyau (nucleus sampling)

0.9 – 1.0

stop

Séquences déclenchant l’arrêt de la génération

["\n\n", "FIN"]

Remarque 21

De nombreuses API proposent désormais des modes de sortie structurée : JSON mode, schémas JSON, grammaires formelles. Ces modes contraignent le décodage pour garantir que la sortie respecte un format precis. Chez Anthropic, on utilise le prefilling (commencer la réponse de l’assistant par {) combine à stop_sequences: ["}"] ; chez OpenAI, le paramètre response_format avec un schéma JSON. Ces mécanismes sont essentiels pour les pipelines automatisés.

L’API Anthropic (Claude)#

L’API d’Anthropic utilise le endpoint Messages (/v1/messages). La bibliothèque Python officielle anthropic encapsule les appels HTTP.

Exemple 12 (Appel simple à l’API Claude)

import anthropic

client = anthropic.Anthropic()  # clé via ANTHROPIC_API_KEY

message = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system="Tu es un assistant pédagogique en informatique.",
    messages=[
        {"role": "user", "content": "Explique la différence entre une pile et une file."}
    ]
)

print(message.content[0].text)
print(f"Tokens utilisés : {message.usage.input_tokens} in, {message.usage.output_tokens} out")

Points clés de l’API Anthropic :

  • Le system prompt est un paramètre séparé (pas un message dans la liste).

  • La réponse est un objet Message contenant une liste de content blocks (texte, outil, image).

  • L’objet usage fournit le décompte exact des tokens consommés.

  • Les modèles disponibles incluent Claude Opus, Sonnet et Haiku, avec des compromis performance/coût différents.

L’API Claude supporte également l’utilisation d’outils (tool use / function calling), permettant au modèle d’appeler des fonctions externes définies par le développeur. Ce mécanisme est fondamental pour la construction d’agents (chapitres 12 a 15).

Exemple 13 (Utilisation d’outils avec Claude)

import anthropic

client = anthropic.Anthropic()

# Définition d'un outil
tools = [
    {
        "name": "get_weather",
        "description": "Obtient la météo actuelle pour une ville donnée.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "Nom de la ville"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["city"]
        }
    }
]

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Quel temps fait-il à Paris ?"}]
)

# Le modèle peut répondre avec un bloc tool_use
for block in response.content:
    if block.type == "tool_use":
        print(f"Outil appele : {block.name}")
        print(f"Arguments : {json.dumps(block.input, indent=2)}")

Lorsque le modèle décide d’utiliser un outil, il retourne un bloc tool_use avec le nom de l’outil et les arguments structurés. Le développeur exécute l’outil, puis renvoie le résultat dans un message tool_result pour que le modèle formule sa réponse finale.

L’API OpenAI#

L’API d’OpenAI utilise le endpoint Chat Completions (/v1/chat/completions). La structure est similaire à celle d’Anthropic, avec quelques différences notables.

Exemple 14 (Appel simple à l’API OpenAI)

from openai import OpenAI

client = OpenAI()  # clé via OPENAI_API_KEY

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=1024,
    messages=[
        {"role": "system", "content": "Tu es un assistant pédagogique en informatique."},
        {"role": "user", "content": "Explique la différence entre une pile et une file."}
    ]
)

print(response.choices[0].message.content)
print(f"Tokens : {response.usage.prompt_tokens} in, {response.usage.completion_tokens} out")

Différences clés avec l’API Anthropic :

  • Le system prompt est un message dans la liste (rôle system), pas un paramètre séparé.

  • La réponse contient une liste de choices (utile pour n > 1).

  • Les noms de champs diffèrent : prompt_tokens vs input_tokens, completion_tokens vs output_tokens.

  • Le format de retour est un objet ChatCompletion, pas un objet Message.

Aspect

Anthropic (Claude)

OpenAI (GPT)

System prompt

Paramètre séparé

Message rôle system

Réponse

message.content[0].text

choices[0].message.content

Tokens entrée

usage.input_tokens

usage.prompt_tokens

Tokens sortie

usage.output_tokens

usage.completion_tokens

Outils

tools + blocs tool_use

tools + tool_calls

Streaming

stream=True (SSE)

stream=True (SSE)

Streaming et latence#

Par défaut, un appel API attend que la génération soit terminée avant de retourner la réponse complète. Le streaming permet de recevoir les tokens au fur et à mesure de leur génération, réduisant le temps de première réponse perçu par l’utilisateur.

Exemple 15 (Streaming avec l’API Claude)

import anthropic

client = anthropic.Anthropic()

# Streaming : les tokens arrivent un par un
with client.messages.stream(
    model="claude-sonnet-4-20250514",
    max_tokens=512,
    messages=[{"role": "user", "content": "Raconte une courte histoire."}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

print()  # retour à la ligne final

Le streaming utilise les Server-Sent Events (SSE) : le serveur envoie des évènements incrémentaux sur une connexion HTTP persistante. Chaque évènement contient un ou plusieurs tokens générés. Côté client, la bibliothèque gère la connexion et expose un itérateur.

Deux métriques de latence sont à distinguer :

  • Time-to-first-token (TTFT) : temps entre l’envoi de la requête et la reception du premier token. Dépend principalement du temps de prefill (traitement du prompt). Avec streaming, l’utilisateur voit la réponse commencer après ce delai.

  • Latence totale : temps entre l’envoi de la requête et la réception du dernier token. Proportionnelle au nombre de tokens générés.

Remarque 22

Le streaming n’est pas qu’une optimisation technique — c’est un choix d”expérience utilisateur. Un modèle qui affiche sa réponse token par token donne une impression de réactivité, même si la latence totale est identique à un appel non-streame. Pour les applications intéractives (chatbots, assistants de code), le streaming est quasi systématique. Pour les pipelines automatisés (classification, extraction), il est inutile et ajoute de la complexité au parsing de la réponse.

Remarque 23

Les API imposent des limites de débit (rate limits) exprimées en requêtes par minute (RPM) et en tokens par minute (TPM). Dépasser ces limites retourne une erreur HTTP 429. Les stratégies de gestion incluent : le backoff exponentiel (attendre un délai croissant avant de réessayer), les files d’attente avec contrôle de concurrence, et la répartition entre plusieurs clés ou fournisseurs. La bibliothèque anthropic gère automatiquement les retries avec backoff.

Gestion de la fenêtre de contexte#

Définition 22 (Fenêtre de contexte)

La fenêtre de contexte (context window) d’un LLM est le nombre maximal de tokens que le modèle peut traiter en une seule requête (prompt + réponse). Elle détermine la quantité d’information que le modèle peut « voir » simultanément. Au-delà de cette limite, les tokens les plus anciens sont tronqués ou la requête est rejetée.

Les tailles de fenêtre de contexte varient considérablement selon les modèles :

Hide code cell source

models = [
    "Claude Opus 4\n(Anthropic)",
    "Claude Sonnet 4\n(Anthropic)",
    "Claude Haiku 3.5\n(Anthropic)",
    "GPT-4o\n(OpenAI)",
    "GPT-4 Turbo\n(OpenAI)",
    "Gemini 1.5 Pro\n(Google)",
    "Mistral Large\n(Mistral)",
    "Llama 3.1 405B\n(Meta)",
]
context_sizes_k = [200, 200, 200, 128, 128, 2000, 128, 128]

fig, ax = plt.subplots(figsize=(12, 6))

colors = ['#4C72B0', '#4C72B0', '#4C72B0', '#DD8452', '#DD8452', '#55A868', '#C44E52', '#8172B3']
bars = ax.barh(range(len(models)), context_sizes_k, color=colors, edgecolor='white', height=0.6)

ax.set_yticks(range(len(models)))
ax.set_yticklabels(models, fontsize=9)
ax.set_xlabel("Taille de la fenêtre de contexte (milliers de tokens)", fontsize=11)
ax.set_title("Fenêtres de contexte des principaux LLM (2025)", fontsize=13)
ax.set_xscale('log')
ax.set_xlim(50, 3000)

for bar, size in zip(bars, context_sizes_k):
    ax.text(bar.get_width() * 1.05, bar.get_y() + bar.get_height() / 2,
            f"{size}k", va='center', fontsize=10, fontweight='bold')

ax.invert_yaxis()
plt.show()
_images/1c2ec561f57eb179471dabe018e47fad0acd99dadf3412909a7eb063d4426669.png

Propriété 5 (Mise à l’échelle de la fenêtre de contexte)

L’augmentation de la fenêtre de contexte n’est pas gratuite. L’auto-attention standard a une complexité en \(O(n^2)\) par rapport à la longueur de la séquence \(n\). Une fenêtre de 200k tokens requiert donc environ \((200/8)^2 = 625\) fois plus de calcul d’attention qu’une fenêtre de 8k tokens. Les fournisseurs utilisent des techniques d’attention optimisée (FlashAttention, attention à fenêtre glissante, KV-cache) pour rendre ces longues fenêtres praticables, mais le coût et la latence augmentent néanmoins avec la longueur du contexte.

Comptage de tokens#

Définition 23 (Comptage de tokens)

Le comptage de tokens consiste à déterminer le nombre de tokens que le tokeniseur d’un modèle donné produit pour un texte donné. C’est une étape cruciale pour : (1) vérifier que le prompt tient dans la fenêtre de contexte, (2) estimer le coût d’un appel, (3) gérer le budget de tokens entre system prompt, historique et réponse.

La bibliothèque tiktoken (OpenAI) permet de compter les tokens pour les modèles GPT. Pour les modèles Claude, Anthropic fournit un endpoint de comptage et la méthode client.count_tokens().

Hide code cell source

# Comptage de tokens avec tiktoken (modeles OpenAI)
enc_gpt4 = tiktoken.encoding_for_model("gpt-4o")
enc_gpt35 = tiktoken.encoding_for_model("gpt-3.5-turbo")

textes = [
    "Bonjour, comment allez-vous ?",
    "L'apprentissage automatique est une branche de l'intelligence artificielle.",
    "def fibonacci(n):\n    if n <= 1:\n        return n\n    return fibonacci(n-1) + fibonacci(n-2)",
    "The quick brown fox jumps over the lazy dog.",
]

print(f"{'Texte':<70} {'GPT-4o':>8} {'GPT-3.5':>8}")
print("-" * 90)
for texte in textes:
    n4 = len(enc_gpt4.encode(texte))
    n35 = len(enc_gpt35.encode(texte))
    affichage = texte[:65] + "..." if len(texte) > 65 else texte
    print(f"{affichage:<70} {n4:>8} {n35:>8}")
Texte                                                                    GPT-4o  GPT-3.5
------------------------------------------------------------------------------------------
Bonjour, comment allez-vous ?                                                 6        7
L'apprentissage automatique est une branche de l'intelligence art...         14       20
def fibonacci(n):
    if n <= 1:
        return n
    return fibo...         28       28
The quick brown fox jumps over the lazy dog.                                 10       10

Hide code cell source

# Visualisation de la tokenisation
texte_demo = "L'intelligence artificielle transforme notre quotidien."
tokens_gpt4 = enc_gpt4.encode(texte_demo)
decoded = [enc_gpt4.decode([t]) for t in tokens_gpt4]

print(f"Texte original : {texte_demo}")
print(f"Nombre de tokens (GPT-4o) : {len(tokens_gpt4)}")
print(f"Tokens : {decoded}")
print(f"IDs    : {tokens_gpt4}")
Texte original : L'intelligence artificielle transforme notre quotidien.
Nombre de tokens (GPT-4o) : 9
Tokens : ['L', "'int", 'elligence', ' artific', 'ielle', ' transforme', ' notre', ' quotidien', '.']
IDs    : [43, 37062, 33465, 105453, 22380, 184109, 12092, 59486, 13]

Exemple 16 (Budget de tokens dans une conversation)

Considérons une fenêtre de contexte de 200 000 tokens et la répartition suivante :

  • System prompt : ~500 tokens (instructions, persona, contraintes)

  • Réponse maximale (max_tokens) : 4 096 tokens

  • Budget restant pour l’historique : \(200\,000 - 500 - 4\,096 = 195\,404\) tokens

Avec une moyenne de ~150 tokens par message (utilisateur + assistant), cela permet environ 1 300 échanges dans une seule conversation. En pratique, les conversations atteignent rarement cette limite, mais les applications de type RAG (chapitre 10) qui injectent de longs documents dans le contexte la consomment rapidement.

# Calcul du budget de tokens
context_window = 200_000
system_tokens = 500
max_output = 4_096
budget_historique = context_window - system_tokens - max_output
tokens_par_echange = 150  # moyenne user + assistant
max_echanges = budget_historique // tokens_par_echange
print(f"Budget historique : {budget_historique:,} tokens")
print(f"Echanges max : ~{max_echanges}")

Hide code cell source

# Calculateur de budget de tokens
def calculer_budget(context_window, system_tokens, max_output, tokens_par_echange=150):
    """Calcule le nombre d'échanges possibles dans une fenêtre de contexte."""
    budget = context_window - system_tokens - max_output
    echanges = budget // tokens_par_echange
    return {
        "context_window": context_window,
        "system_tokens": system_tokens,
        "max_output": max_output,
        "budget_historique": budget,
        "tokens_par_echange": tokens_par_echange,
        "echanges_max": echanges,
        "utilisation_pct": (system_tokens + max_output) / context_window * 100
    }

# Comparaison pour différents modèles
configs = [
    ("Claude (200k)", 200_000, 500, 4096),
    ("GPT-4o (128k)", 128_000, 500, 4096),
    ("Mistral Large (128k)", 128_000, 500, 4096),
    ("Gemini 1.5 Pro (2M)", 2_000_000, 500, 8192),
]

print(f"{'Modèle':<28} {'Budget':>12} {'Echanges max':>14}")
print("-" * 58)
for nom, cw, sp, mo in configs:
    r = calculer_budget(cw, sp, mo)
    print(f"{nom:<28} {r['budget_historique']:>12,} {r['echanges_max']:>14,}")
Modèle                             Budget   Echanges max
----------------------------------------------------------
Claude (200k)                     195,404          1,302
GPT-4o (128k)                     123,404            822
Mistral Large (128k)              123,404            822
Gemini 1.5 Pro (2M)             1,991,308         13,275

Lorsque la fenêtre de contexte est insuffisante, plusieurs stratégies sont possibles :

  1. Troncation : supprimer les messages les plus anciens de l’historique. Simple mais perd du contexte.

  2. Résumé : utiliser le LLM pour résumer périodiquement l’historique, puis remplacer les messages anciens par le résumé.

  3. RAG : ne pas stocker tout le contexte dans la fenêtre, mais le récupérer dynamiquement depuis une base de connaissances (chapitre 10).

  4. Fenêtre glissante : conserver les \(k\) derniers échanges. Combiné avec un résumé des échanges précédents.

Remarque 24

En pratique, la gestion du contexte est l’un des défis majeurs des applications LLM en production. Le system prompt doit être aussi court que possible tout en restant précis. Les documents injectés (dans un pipeline RAG) doivent être décomposés en morceaux (chunks) de taille appropriée — trop petits, ils perdent le contexte local ; trop grands, ils saturent la fenêtre. L’ajout de metadata (source, date, score de pertinence) consomme aussi des tokens. Un budget de tokens bien géré est souvent la différence entre un prototype et un produit fiable.

Coûts et optimisation#

L’utilisation d’API de LLM est facturée par token, avec des tarifs différents pour les tokens d”entrée (prompt) et de sortie (génération). La sortie est généralement 3 à 5 fois plus coûteuse que l’entrée.

Remarque 25

L’estimation des coûts est critique pour la viabilité d’un projet. Un chatbot traitant 10 000 conversations par jour, avec 2 000 tokens par conversation (entrée + sortie), consomme 20 millions de tokens par jour. A 3 $/million de tokens en entrée et 15 $/million en sortie (tarifs Claude Sonnet), cela représente un budget significatif. L’optimisation passe par le choix du modèle adapté (Haiku pour les tâches simples, Sonnet pour les tâches moyennes, Opus pour les tâches complexes), la réduction de la longueur des prompts, et l’utilisation du prompt caching.

Hide code cell source

# Coûts par million de tokens (USD, tarifs approximatifs 2025)
modeles_cout = {
    "Claude Opus 4": {"input": 15.0, "output": 75.0, "provider": "Anthropic"},
    "Claude Sonnet 4": {"input": 3.0, "output": 15.0, "provider": "Anthropic"},
    "Claude Haiku 3.5": {"input": 0.80, "output": 4.0, "provider": "Anthropic"},
    "GPT-4o": {"input": 2.50, "output": 10.0, "provider": "OpenAI"},
    "GPT-4o mini": {"input": 0.15, "output": 0.60, "provider": "OpenAI"},
    "Gemini 1.5 Pro": {"input": 1.25, "output": 5.0, "provider": "Google"},
    "Mistral Large": {"input": 2.0, "output": 6.0, "provider": "Mistral"},
}

def estimer_cout(modele, input_tokens, output_tokens):
    """Estime le coût d'un appel API en USD."""
    tarifs = modeles_cout[modele]
    cout_input = (input_tokens / 1_000_000) * tarifs["input"]
    cout_output = (output_tokens / 1_000_000) * tarifs["output"]
    return {
        "modele": modele,
        "input_tokens": input_tokens,
        "output_tokens": output_tokens,
        "cout_input": cout_input,
        "cout_output": cout_output,
        "cout_total": cout_input + cout_output,
    }

# Estimation pour un appel typique
resultat = estimer_cout("Claude Sonnet 4", input_tokens=1500, output_tokens=500)
print(f"Modèle : {resultat['modele']}")
print(f"Tokens : {resultat['input_tokens']} in + {resultat['output_tokens']} out")
print(f"Coût   : ${resultat['cout_total']:.6f}")
print(f"  - Entrée  : ${resultat['cout_input']:.6f}")
print(f"  - Sortie  : ${resultat['cout_output']:.6f}")
Modèle : Claude Sonnet 4
Tokens : 1500 in + 500 out
Coût   : $0.012000
  - Entrée  : $0.004500
  - Sortie  : $0.007500

Hide code cell source

# Comparaison des coûts par fournisseur
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

noms = list(modeles_cout.keys())
couts_input = [modeles_cout[m]["input"] for m in noms]
couts_output = [modeles_cout[m]["output"] for m in noms]

provider_colors = {
    "Anthropic": "#4C72B0",
    "OpenAI": "#DD8452",
    "Google": "#55A868",
    "Mistral": "#C44E52",
}
colors = [provider_colors[modeles_cout[m]["provider"]] for m in noms]

# Coût input
bars_in = axes[0].barh(range(len(noms)), couts_input, color=colors, edgecolor='white', height=0.6)
axes[0].set_yticks(range(len(noms)))
axes[0].set_yticklabels(noms, fontsize=9)
axes[0].set_xlabel("USD / million de tokens", fontsize=10)
axes[0].set_title("Cout des tokens d'entree", fontsize=12)
axes[0].invert_yaxis()
for bar, cost in zip(bars_in, couts_input):
    axes[0].text(bar.get_width() + 0.2, bar.get_y() + bar.get_height() / 2,
                 f"${cost:.2f}", va='center', fontsize=9)

# Coût output
bars_out = axes[1].barh(range(len(noms)), couts_output, color=colors, edgecolor='white', height=0.6)
axes[1].set_yticks(range(len(noms)))
axes[1].set_yticklabels(noms, fontsize=9)
axes[1].set_xlabel("USD / million de tokens", fontsize=10)
axes[1].set_title("Coût des tokens de sortie", fontsize=12)
axes[1].invert_yaxis()
for bar, cost in zip(bars_out, couts_output):
    axes[1].text(bar.get_width() + 0.5, bar.get_y() + bar.get_height() / 2,
                 f"${cost:.2f}", va='center', fontsize=9)

# Légende
patches = [mpatches.Patch(color=c, label=p) for p, c in provider_colors.items()]
fig.legend(handles=patches, loc='lower center', ncol=4, fontsize=10,
           bbox_to_anchor=(0.5, -0.02))
fig.suptitle("Comparaison des tarifs API par modèle (2025)", fontsize=13, y=1.02)
plt.show()
_images/beb1bccddf174e93d79b8224a28cfe53838cb284d2f55bcdbfa49216d8a45fad.png

Prompt caching#

Le prompt caching est une optimisation proposée par Anthropic et OpenAI qui permet de réutiliser le traitement (prefill) d’un préfixe de prompt identique entre plusieurs requêtes. Lorsque le début du prompt est identique (system prompt, instructions longues, documents injectés), le fournisseur met en cache les représentations internes, réduisant à la fois la latence et le coût.

Chez Anthropic, le prompt caching réduit le coût des tokens cachés de 90 % et le TTFT de manière significative. C’est particulièrement avantageux pour les applications RAG où le même document est interrogé plusieurs fois.

Remarque 26

Le prompt caching est particulièrement efficace lorsqu’un préfixe long et stable est réutilisé entre les requêtes. Cas d’usage typiques : (1) un system prompt détaillé avec des instructions complexes, (2) un document injecté pour du RAG, interrogé par plusieurs utilisateurs, (3) un contexte de few-shot avec de nombreux exemples. Le cache a une durée de vie limitée (5 minutes chez Anthropic, variable chez OpenAI) et n’est pas garanti — il faut concevoir l’application pour fonctionner correctement avec ou sans cache.

Batch API#

Les API batch permettent d’envoyer un lot de requêtes à traiter de manière asynchrone, avec un tarif réduit (typiquement 50 % de réduction). Le temps de traitement est plus long (jusqu’à 24 heures), mais le coût par token est considérablement réduit. Cette approche convient aux taches non intéractives : évaluation de datasets, génération massive de contenu, classification en lot.

Hide code cell source

# Simulateur de coût pour différents scénarios
scenarios = [
    ("Chatbot (1k conv/jour)", 1000, 1500, 500, 30),
    ("RAG (500 queries/jour)", 500, 5000, 1000, 30),
    ("Classification (10k/jour)", 10000, 200, 50, 30),
    ("Génération (100 articles/jour)", 100, 500, 3000, 30),
]

print(f"{'Scenario':<35} {'Tokens/mois':>14} {'Sonnet':>12} {'Haiku':>12} {'GPT-4o mini':>12}")
print("-" * 90)

for nom, n_requetes, tok_in, tok_out, jours in scenarios:
    total_in = n_requetes * tok_in * jours
    total_out = n_requetes * tok_out * jours
    total = total_in + total_out

    cout_sonnet = estimer_cout("Claude Sonnet 4", total_in, total_out)["cout_total"]
    cout_haiku = estimer_cout("Claude Haiku 3.5", total_in, total_out)["cout_total"]
    cout_mini = estimer_cout("GPT-4o mini", total_in, total_out)["cout_total"]

    print(f"{nom:<35} {total:>14,} ${cout_sonnet:>10.2f} ${cout_haiku:>10.2f} ${cout_mini:>10.2f}")
Scenario                               Tokens/mois       Sonnet        Haiku  GPT-4o mini
------------------------------------------------------------------------------------------
Chatbot (1k conv/jour)                  60,000,000 $    360.00 $     96.00 $     15.75
RAG (500 queries/jour)                  90,000,000 $    450.00 $    120.00 $     20.25
Classification (10k/jour)               75,000,000 $    405.00 $    108.00 $     18.00
Génération (100 articles/jour)          10,500,000 $    139.50 $     37.20 $      5.62

Hide code cell source

# Visualisation coût mensuel par scénario et modèle
fig, ax = plt.subplots(figsize=(12, 6))

scenario_noms = [s[0] for s in scenarios]
modeles_plot = ["Claude Sonnet 4", "Claude Haiku 3.5", "GPT-4o mini"]
couleurs = ["#4C72B0", "#55A868", "#DD8452"]

x = np.arange(len(scenario_noms))
width = 0.22

for i, (modele, couleur) in enumerate(zip(modeles_plot, couleurs)):
    couts = []
    for nom, n_req, tok_in, tok_out, jours in scenarios:
        total_in = n_req * tok_in * jours
        total_out = n_req * tok_out * jours
        c = estimer_cout(modele, total_in, total_out)["cout_total"]
        couts.append(c)
    bars = ax.bar(x + i * width, couts, width, label=modele, color=couleur, edgecolor='white')
    for bar, cout in zip(bars, couts):
        ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 1,
                f"${cout:.0f}", ha='center', va='bottom', fontsize=8)

ax.set_xticks(x + width)
ax.set_xticklabels(scenario_noms, fontsize=9)
ax.set_ylabel("Coût mensuel (USD)", fontsize=11)
ax.set_title("Estimation du coût mensuel par scénario et modèle", fontsize=13)
ax.legend(fontsize=10)
plt.show()
_images/c6f815c1198afb1385afef76d0199aba2a0f54cd4c96d3c652e24a072c587d1e.png

Résumé#

Ce chapitre a presenté les aspects pratiques de l’utilisation des API de LLM, depuis les arbitrages fondamentaux jusqu’à l’optimisation des coûts.

  1. Les API distantes offrent un accès immédiat aux modèles les plus performants sans gérer l’infrastructure, tandis que les modèles locaux préservent la confidentialité et permettent un contrôle total. Les architectures hybrides combinent souvent les deux.

  2. La structure d’un appel API repose sur un format de messages avec trois rôles (system, user, assistant) et des paramètres de génération (temperature, max_tokens, top_p, stop). Le system prompt est le principal levier de cadrage du comportement du modèle.

  3. L”API Anthropic (Claude) et l”API OpenAI (GPT) partagent une logique commune mais diffèrent dans les détails : position du system prompt, structure de la réponse, nommage des champs. Les deux supportent le tool use et le streaming.

  4. Le streaming réduit le temps perçu de première réponse en transmettant les tokens au fur et à mesure de leur génération via Server-Sent Events. La distinction entre TTFT et latence totale est essentielle pour l’expérience utilisateur.

  5. La fenêtre de contexte détermine la quantité d’information accessible au modèle en une seule requête. Sa gestion — troncation, résumé, RAG, fenêtre glissante — est un enjeu central des applications conversationnelles et documentaires.

  6. Les coûts sont facturés par token, avec des tarifs différenciés entre entrée et sortie. Le choix du modèle adapté à la tache, le prompt caching, les API batch et la réduction de la longueur des prompts sont les principaux leviers d’optimisation.

  7. La sécurité des clés d’API et la gestion des limites de débit (rate limiting) sont des prérequis pour tout déploiement en production. Les bibliothèques clientes officielles fournissent des mécanismes de retry et de backoff automatiques.

  8. Le chapitre suivant abordera le prompt engineering (chapitre 5), ou nous verrons comment rédiger des prompts efficaces pour tirer le meilleur parti de ces API.