Premier appel API + streaming

Avant d’écrire la moindre ligne de code IA, il faut comprendre trois concepts fondamentaux. Tout le reste en découle.

Tokens : l’unité de facturation

Les LLMs ne raisonnent pas en mots ni en caractères. Ils raisonnent en tokens — des fragments de texte découpés par un tokenizer propre à chaque modèle.

Ordre de grandeur : 1 token ≈ 0.75 mot en anglais. En français, le ratio est moins favorable — les accents, les mots plus longs et la grammaire plus complexe produisent davantage de tokens pour la même quantité d’information.

Comprendre les tokens, c’est comprendre deux choses :

  • Les coûts — chaque token d’entrée et de sortie est facturé.
  • Les limites de contexte — chaque modèle a une fenêtre maximale (en tokens) qu’il peut traiter en un seul appel.

Messages array : la structure fondamentale

Tout appel à un LLM repose sur un tableau de messages avec trois rôles :

  • system — Les instructions permanentes. Persona, contraintes, format de sortie. Envoyé à chaque appel, le modèle s’y conforme tout au long de la conversation.
  • user — L’input humain. La question, la demande, le document à analyser.
  • assistant — La réponse du modèle. Incluse dans l’historique pour maintenir la cohérence conversationnelle.

Point critique : le modèle n’a pas de mémoire native. Tout l’historique de la conversation doit etre renvoyé à chaque appel. Si tu ne renvoies pas les messages précédents, le modèle ne sait pas ce qui a été dit avant.

Streaming : ne jamais attendre la réponse complète

Le modèle génère les tokens un par un. Attendre la fin complète de la génération avant d’afficher quoi que ce soit donne une impression de latence insupportable — parfois 10 à 30 secondes de vide.

Le streaming permet d’afficher chaque token dès qu’il est produit. Coté serveur, c’est un ReadableStream. Coté client, EventSource ou un fetch reader.

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic() // ANTHROPIC_API_KEY en variable d'env

export async function POST(req: Request) {
  const { messages } = await req.json()

  const stream = await client.messages.stream({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    messages
  })

  return stream.toReadableStream()
}

L’utilisateur voit la réponse apparaitre mot par mot. L’expérience passe de “est-ce que c’est planté ?” à “le modèle est en train de réfléchir”.


Prompt Engineering : les patterns qui marchent

Le prompt engineering n’est pas de la magie. C’est de l’ingénierie — des patterns reproductibles qui améliorent la qualité des réponses de manière prévisible.

System prompt : le contrat permanent

Le system prompt transforme un modèle généraliste en un outil spécialisé. C’est la différence entre un chatbot générique qui répond à tout et un produit utilisable dans un contexte métier.

Un bon system prompt définit :

  • La persona — qui est le modèle dans ce contexte.
  • Les contraintes de domaine — sur quoi il peut et ne peut pas répondre.
  • Le format de sortie — comment il doit structurer ses réponses.
  • La langue — éviter les réponses en anglais quand le produit est francophone.
  • Les garde-fous — ce qu’il doit faire quand il ne sait pas.
const systemPrompt = `Tu es un assistant spécialisé pour [NOM CLIENT].

Règles strictes :
- Répondre UNIQUEMENT sur les sujets liés à [DOMAINE]
- Format : bullet points courts, maximum 5 points
- Si l'information n'est pas disponible, dire explicitement "Je ne sais pas"
- Langue : français uniquement
- Ne jamais inventer de données chiffrées`

Few-shot prompting : montrer plutot qu’expliquer

Plutot que de décrire le format attendu en texte, donne des exemples concrets entrée/sortie directement dans le prompt. Le modèle apprend par analogie — lui montrer 2-3 exemples du format exact que tu veux est plus fiable qu’un paragraphe d’instructions.

Cas typique : classification de tickets support. Au lieu de décrire les catégories, montre 3 tickets classifiés. Le modèle reproduira le pattern.

Chain-of-thought (CoT) : penser étape par étape

Demander au modèle de décomposer son raisonnement avant de donner sa réponse finale améliore significativement la qualité sur les taches complexes : calculs, analyses multi-critères, résolution de problèmes.

Le principe : ajouter “Raisonne étape par étape avant de répondre” dans le prompt force le modèle à expliciter ses étapes intermédiaires au lieu de sauter directement à la conclusion.

Le cout : plus de tokens de sortie. Utiliser le CoT uniquement quand la tache le justifie — classification simple ou extraction de données n’en ont pas besoin.

Temperature et top-p : le curseur créativité

  • Temperature 0 — Réponses déterministes. Pour le code, l’extraction JSON, la classification. Le modèle choisit systématiquement le token le plus probable.
  • Temperature 0.7-1 — Réponses variées. Pour la rédaction, le brainstorming, la génération créative.
  • Ne jamais laisser la valeur par défaut sans y avoir réfléchi. La temperature par défaut (souvent 1) n’est pas optimale pour la majorité des cas d’usage en production.

Sorties structurées et JSON mode

Le vrai pouvoir d’un LLM en production, ce n’est pas de générer du texte libre. C’est de retourner des données structurées exploitables par le reste de l’application.

JSON mode et Tool use

Tous les fournisseurs majeurs permettent de forcer le modèle à retourner du JSON conforme à un schéma précis. C’est la clé pour brancher la sortie IA sur une vraie application : base de données, API, interface utilisateur.

Deux approches :

  • JSON mode — Le modèle retourne directement du JSON. Simple mais moins contraignant sur le schéma.
  • Tool use / Function calling — Le modèle “appelle” une fonction avec des paramètres typés. Plus robuste, le schéma est validé coté fournisseur.

Validation de schéma : ne jamais faire confiance

Les modèles respectent le schéma demandé à environ 98%. Pas 100%. Ce 2% d’erreur, c’est un crash en production si tu ne valides pas.

Toujours valider le JSON retourné coté serveur avant de l’utiliser :

import { z } from 'zod'

const ContactSchema = z.object({
  name: z.string(),
  email: z.string().email().optional(),
  intent: z.enum(['achat', 'support', 'information', 'autre']),
  urgency: z.number().min(1).max(10),
  summary: z.string().max(200)
})

Zod en TypeScript, Pydantic en Python. Le schéma devient la source de vérité, pas la sortie du modèle.

Parsing défensif

Prévoir systématiquement le cas ou le modèle retourne du texte libre au lieu du JSON attendu. Ca arrive quand le modèle “refuse” la requête, quand le prompt est ambigu, ou quand le modèle hallucine un format.

Le fallback est obligatoire : logger l’erreur, retourner un message d’erreur utilisable, ne jamais crasher.


Gestion du contexte et mémoire

La gestion du contexte est le problème d’ingénierie central de toute application LLM non-triviale. Le modèle n’a pas de mémoire persistante — c’est au développeur de la simuler.

Context window : la limite dure

Chaque modèle a une fenêtre de contexte maximale en tokens : 128k pour Claude Sonnet 4, 128k pour GPT-4o. Cette limite inclut tout : le system prompt, l’historique des messages, les documents injectés, et la réponse à générer.

A chaque appel, tout l’historique est renvoyé. Le cout croît donc avec la longueur de la conversation.

Sliding window : le pattern standard

Pour les conversations longues, garder les N derniers messages et remplacer les plus anciens par un résumé. C’est le compromis entre fidélité de l’historique et cout.

Le system prompt (premier message) est toujours conservé. Les 10 derniers messages aussi. Tout ce qui est entre les deux est candidat à la compression.

Summarization memory

Quand la fenêtre de contexte approche de sa limite, résumer automatiquement les anciens échanges. Le résumé remplace les messages d’origine — on perd le verbatim mais on garde l’essentiel.

type Message = { role: string; content: string }

async function trimContext(messages: Message[], maxTokens = 80000) {
  let total = estimateTokens(messages)

  while (total > maxTokens && messages.length > 2) {
    const toSummarize = messages.slice(1, -10)
    const summary = await summarize(toSummarize)

    messages = [
      messages[0],
      { role: 'user', content: `[Résumé des échanges précédents]: ${summary}` },
      ...messages.slice(-10)
    ]
    total = estimateTokens(messages)
  }
  return messages
}

Prompt caching : réduire les couts récurrents

Anthropic et OpenAI permettent de cacher les préfixes longs des conversations : system prompt, documents de référence, historique stable. Les tokens cachés sont facturés jusqu’à 90% moins cher.

En pratique, cela signifie que si ton system prompt fait 5 000 tokens et que tu fais 100 appels, tu ne paies le prix plein que pour le premier. Les 99 suivants bénéficient du cache.


Couts réels, erreurs et rate limits

Déployer un LLM en production sans gérer les couts et les erreurs, c’est une bombe à retardement. Cette section couvre les garde-fous indispensables.

Calcul de couts réels

Les tokens d’entrée et de sortie sont facturés à des tarifs différents. En règle générale, les tokens de sortie coutent 3x à 10x plus cher que les tokens d’entrée.

Avant de déployer, modélise le cout par utilisateur par jour :

  • Nombre moyen de messages par session
  • Taille moyenne des messages (en tokens)
  • Nombre de sessions par jour
  • Modèle utilisé et ses tarifs

Un utilisateur actif sur Claude Sonnet peut facilement couter 0.50 à 2 EUR/jour en tokens. Multiplie par ta base d’utilisateurs.

Retry avec backoff exponentiel

Les erreurs 429 (rate limit) et 529 (surcharge) sont normales en production. Tous les fournisseurs ont des limites de requêtes par minute. L’application ne doit jamais crasher sur ces erreurs.

async function callWithRetry(fn: () => Promise<any>, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (err: any) {
      if (err.status === 429 || err.status === 529) {
        const delay = Math.pow(2, i) * 1000
        await new Promise(r => setTimeout(r, delay))
        continue
      }
      throw err
    }
  }
  throw new Error('Max retries reached')
}

Le délai double à chaque tentative : 1s, 2s, 4s. Simple et efficace.

Model fallback

Si le modèle principal est indisponible ou trop lent, basculer automatiquement sur un modèle alternatif. Par exemple : Claude Sonnet en principal, GPT-4o-mini en fallback.

La réponse sera peut-etre de qualité inférieure, mais l’application reste fonctionnelle. La résilience prime sur la perfection.

Timeout et AbortController

Les appels LLM peuvent durer 30 à 60 secondes sur des prompts longs. Deux impératifs :

  • Définir un timeout coté serveur pour éviter les requêtes suspendues indéfiniment.
  • Permettre à l’utilisateur d’annuler coté client via un AbortController.

Budget cap par utilisateur

Le garde-fou le plus important et le plus souvent oublié.

async function checkBudget(userId: string) {
  const dailyTokens = await getDailyUsage(userId)
  if (dailyTokens > MAX_DAILY_TOKENS) {
    throw new Error('daily_limit_reached')
  }
}

Limiter la consommation par utilisateur par jour directement dans le code. Sans cette limite, un utilisateur malveillant ou un bug de boucle peut exploser le budget API en quelques minutes.


Projet : LLM Playground

Le projet de ce module est une interface web pour comparer des prompts sur plusieurs modèles, visualiser les tokens utilisés et estimer les couts en temps réel.

C’est un outil que tu utiliseras toi-même au quotidien — et un démonstrateur puissant pour tes clients.

Étapes de réalisation

  1. Interface 2 colonnes — Prompt input à gauche, réponse streamée à droite. Le minimum pour itérer rapidement sur des prompts.
  2. Sélecteur de modèle — Claude Sonnet, GPT-4o-mini, Gemini Flash. Comparer la même requête sur plusieurs modèles coté à coté.
  3. Compteur de tokens et cout estimé en temps réel — Afficher le nombre de tokens d’entrée/sortie et le cout estimé pendant le streaming. Rend le cout tangible.
  4. Sauvegarde des prompts — localStorage pour commencer, base de données pour persister. Pouvoir revenir sur un prompt qui marchait bien.
  5. Export des résultats en JSON/CSV — Pour documenter les tests, comparer les performances, justifier le choix d’un modèle auprès d’un client.
  6. Déploiement — Vercel, Railway, ou équivalent. L’outil doit être accessible en ligne pour le montrer à un client ou le partager avec une équipe.