iaproductionarchitecture

Industrialiser un MVP IA : cache, streaming, file d'attente, fallbacks

Un MVP IA qui marche en démo et un produit qui tient en prod ne sont pas séparés par plus de fonctionnalités, mais par quatre briques d'architecture précises.

Le MVP qui marche "presque" n'a pas un problème de fonctionnalités

Un MVP IA qui fonctionne en démo et qui casse en prod suit toujours le même schéma : un script qui appelle l'API d'un LLM et renvoie le résultat, écrit vite pour valider une hypothèse — pas pour tenir sous charge. C'est normal, et ce n'est pas un défaut de conception initial : un MVP est fait pour être jeté ou industrialisé, jamais pour rester tel quel.

Le problème apparaît quand il faut passer à l'étape suivante, et que "industrialiser" reste un mot vague. Concrètement, ça se joue sur quatre briques.

L'architecture : séparer la logique métier des appels IA

Le MVP typique mélange logique produit et appel au modèle dans la même fonction. Dès que le modèle change de version, ou qu'on veut router entre Anthropic et DeepSeek selon le cas d'usage, chaque appel dispersé dans le code devient un point de friction. La bascule vers un produit passe par une API structurée : une couche d'orchestration explicite (Langchain, Langraph pour les workflows agent) qui isole les appels IA du reste, avec une gestion d'erreur propre et un point de bascule unique quand le modèle change.

// point d'entrée unique — le reste de l'app ne connaît jamais le provider
async function completeWithFallback(prompt: string): Promise<string> {
  try {
    return await anthropic.complete(prompt);
  } catch (e) {
    return await deepseek.complete(prompt); // fallback, pas de plantage utilisateur
  }
}

Le cache : la première dépense qu'on peut couper sans rien perdre

Un MVP envoie souvent des prompts trop longs, ne met rien en cache, et refait les mêmes appels pour les mêmes données. Un cache sémantique sur les requêtes récurrentes réduit directement la facture d'appels sans changer le résultat perçu par l'utilisateur. Ce n'est pas une optimisation de confort — c'est souvent la ligne budgétaire qui décide si le produit reste viable à l'usage réel.

La file d'attente : ce qui absorbe la charge que la démo ne teste jamais

Quelques utilisateurs en démo, ça passe toujours. La production, avec des pics d'usage imprévisibles, demande une architecture qui ne bloque pas l'interface le temps qu'un appel LLM réponde. Des appels asynchrones traités via une file d'attente (Redis en backend de queue, par exemple) évitent qu'un pic de charge fasse tomber l'ensemble du service — la requête attend son tour au lieu de faire échouer la requête suivante.

Le streaming et les fallbacks : ce que l'utilisateur voit pendant que ça tourne

Un appel IA prend du temps. Un MVP affiche un spinner et espère. Un produit affiche la réponse au fur et à mesure qu'elle arrive (streaming), avec un état de chargement qui reste informatif, et un comportement de repli explicite quand le modèle met trop de temps ou échoue — plutôt qu'un écran figé qui laisse l'utilisateur deviner si quelque chose s'est cassé.

Ce que je ne fais pas

Je ne reconstruis pas tout de zéro. La peur classique — "il va jeter mon code et repartir de zéro" — n'est fondée que si le code est réellement irrécupérable, ce qui est rare. La bascule se fait brique par brique : on isole d'abord la couche d'appel IA, on ajoute le cache, on introduit la file d'attente, on branche le streaming — chaque étape livrée séparément, sans geler le produit pendant des semaines.

Le signal qui dit qu'il est temps

Le signal n'est pas "j'ai des utilisateurs". C'est le moment où la peur de faire une démo en live devient réelle, où la facture d'API grimpe plus vite que l'usage ne le justifie, ou où chaque nouvelle fonctionnalité prend plus de temps que la précédente parce que tout est imbriqué autour du même script initial. Ce moment n'arrive pas à un chiffre d'utilisateurs précis — il arrive quand le coût de continuer à bricoler dépasse le coût de poser une vraie architecture.