case_study.mdÉtude de cas
KeepForLater
Transformer l’infobésité en savoir interrogeable
Un SaaS « second cerveau IA » conçu, développé et déployé seul, de l’architecture à la facturation. Cette page détaille les décisions d’ingénierie qui font tenir un système IA en production — et pas seulement en démo.
Voir le produit en ligne- Rôle
- Produit personnel, seul aux commandes — archi, code, production
- Surfaces
- Web SSR + PWA · extension navigateur · serveur MCP
- Cœur technique
- Workflows LLM durables · RAG hybride · sorties structurées
- Exploitation
- Télémétrie par appel · évals en continu · environnements séparés
Le problème
On accumule des heures de vidéos, de podcasts, d’articles et de newsletters qu’on ne relira jamais. L’information est sauvegardée, mais elle n’est pas devenue du savoir : impossible de retrouver l’argument précis entendu il y a trois mois, encore moins de le citer avec sa source.
Les assistants génériques répondent à côté sur ce terrain : ils ne connaissent pas vos sources, inventent des références et ne savent pas dire d’où vient une affirmation. L’enjeu n’était donc pas de brancher un modèle de langage, mais de construire une chaîne complète — ingestion, distillation, indexation, restitution sourcée — qui reste fiable et dont le coût reste prévisible.
Les contraintes
- Un traitement IA qui échoue à mi-parcours ne doit jamais refacturer les étapes déjà réussies.
- Toute réponse doit être traçable jusqu’au passage exact de la source. Sans citation vérifiable, la réponse ne vaut rien.
- Le coût par action doit être connu avant d’être facturé, sinon le modèle économique se découvre en fin de mois.
- Les données d’un utilisateur ne doivent jamais croiser celles d’un autre, y compris dans l’index vectoriel.
- Un développeur seul : chaque choix devait réduire la surface à opérer, pas l’augmenter.
L’architecture, étape par étape
Ingestion : un workflow qui survit aux pannes
Ingérer une vidéo d’une heure enchaîne cinq opérations lentes et coûteuses : extraction, distillation, catégorisation, calcul des vecteurs, finalisation. Écrites naïvement, elles forment une longue fonction où la moindre erreur réseau perd tout le travail déjà payé.
Chaque étape est donc une étape de workflow durable, checkpointée et idempotente. Une étape qui échoue est réessayée seule, avec le résultat des précédentes déjà en mémoire : si le calcul des vecteurs tombe, la transcription n’est pas relancée — donc pas refacturée.
- Transcription des médias longs par segmentation en plages d’octets, ce qui contourne la limite de sortie du modèle sans dégrader la qualité.
- Sept types de veille automatisée (chaînes et playlists YouTube, subreddits, flux RSS, mots-clés, digest hebdomadaire) déclenchés par tâches planifiées.
Distillation : le modèle rend du JSON, pas de la prose
Toute la distillation — résumé court, points clés, concepts, résumé narratif, chapitrage horodaté — sort en JSON validé par schéma. Le schéma est déclaré une seule fois et partagé entre le serveur, le client et l’extension : si un champ change, le compilateur le signale partout avant le déploiement.
Le modèle s’auto-évalue également sur sa fidélité à la source, et ce score est affiché dans l’interface. L’utilisateur sait quand une synthèse s’éloigne du matériau d’origine, au lieu de devoir la croire sur parole.
Recherche : hybride, parce que le vectoriel seul échoue
La recherche vectorielle seule rate ce qui compte le plus souvent : un nom propre, un acronyme, une référence exacte. La recherche lexicale seule rate le sens. Les deux tournent donc en parallèle et leurs résultats sont fusionnés par rang réciproque, ce qui fait remonter les passages que les deux méthodes jugent pertinents.
Le texte est découpé en fragments sémantiques à chevauchement pour qu’une idée à cheval sur deux paragraphes ne soit jamais coupée en deux. Les résultats sont ensuite diversifiés par source, afin qu’un seul document verbeux ne monopolise pas la réponse.
Chaque réponse cite ses sources, et cliquer une citation ouvre le passage exact, surligné. La recherche traverse les langues : une question en français retrouve un passage en anglais, par le sens.
- Un harnais d’évaluation rejouable mesure la qualité de la recherche à chaque modification — une régression se voit avant la mise en production, pas après.
- L’isolation par utilisateur est appliquée au niveau des requêtes SQL, y compris sur l’index vectoriel.
Exposition : trois surfaces, un seul contrat
Le produit vit sur trois surfaces : une application web rendue côté serveur et installable, une extension navigateur pour capturer une page en un clic, et un serveur MCP. Toutes appellent la même interface typée de bout en bout — le client connaît les routes du serveur à la compilation, sans code généré ni documentation à maintenir.
Le serveur MCP est le choix le plus structurant : il expose la base de connaissances comme un jeu d’outils utilisable directement depuis Claude, ChatGPT ou un éditeur de code. L’utilisateur n’a pas à venir dans l’application — il amène son propre assistant.
- Rétention active : quiz générés à la demande, révision espacée, graphe de connaissances reliant les sources par co-occurrence et proximité sémantique.
- Synthèse vocale avec cache en périphérie et flux podcast privé, pour réécouter ses propres synthèses en déplacement.
Économie et conformité : mesurer avant de facturer
Un produit IA se casse sur ses coûts avant de se casser sur sa technique. Chaque appel au modèle remonte sa consommation réelle, qui est enregistrée par action. Le système de crédits est calé sur ce coût mesuré : la marge est connue avant la facture, pas découverte après.
Côté conformité, les outils de mesure d’audience et de suivi d’erreurs ne se chargent qu’après consentement explicite, et les données d’un compte sont cloisonnées puis réellement supprimées à la demande.
Exploitation : rendre le pipeline mesurable
Un système IA en production pose trois questions auxquelles le code seul ne répond pas : combien coûte réellement une action, quel prompt a produit ce résultat, et la qualité a-t-elle baissé depuis la dernière mise en ligne. Trois questions, trois dispositifs.
La télémétrie écrit une ligne par appel réellement émis. Y compris les appels ratés : le fournisseur facture les jetons d’une génération qui échoue en cours de route, et les masquer sous-estimerait le coût. Un coût inconnu est enregistré comme inconnu, jamais comme zéro — un zéro se lirait « appel gratuit » et fausserait tout total en aval. L’écriture est groupée et hors du chemin critique : une panne d’observabilité fait perdre la mesure, jamais l’ingestion que l’utilisateur a payée.
Les prompts restent dans le dépôt — Git est le versionnage. Un registre n’en stocke que l’identité : identifiant, version, empreinte du texte. À chaque intégration, la chaîne de vérification recalcule l’empreinte du prompt réellement envoyé au modèle et refuse un texte modifié sans changement de version. C’est le pivot de tout le reste : sans cette garde, les mesures d’avant et d’après se mélangent sous une même version et l’historique devient inexploitable.
Les évaluations tournent sur trois couches. Des vérifications déterministes et gratuites à chaque contribution : longueur, structure, langue, et surtout ancrage — chaque concept et chaque chiffre de la synthèse existent-ils dans la source ? C’est un indicateur d’hallucination objectif et vérifiable. Puis un juge indépendant, sur un modèle différent de celui évalué. Enfin une campagne qui ne demande pas « est-ce bon ? » mais « à partir de quand ça casse ? ».
- Les traces partent vers un outil d’observabilité externe, en asynchrone et hors du chemin critique. Le client officiel ne démarre pas sur ce runtime : l’API d’ingestion étant un simple appel authentifié, elle est appelée directement, en une centaine de lignes et sans dépendance.
- Un exportateur qui avale ses erreurs par conception ne se suppose pas, il se prouve : un test de bout en bout envoie une trace par le chemin de production exact, la relit avec ses observations, et nomme le projet auquel les clés appartiennent — la cause classique d’un « le test passe mais l’écran est vide ».
- Développement, test et production portent chacun leur étiquette sur les traces : une régression détectée sur l’environnement de test ne pollue plus les tableaux de production.
- Supprimer un compte anonymise les mesures au lieu de les effacer. Les effacer ferait changer rétroactivement le coût d’un mois déjà clos à chaque départ d’utilisateur — une comptabilité qui se réécrit toute seule ne permet plus de suivre une marge.
Ce que la mesure a révélé
Six analyses, et ce qu’elles ont changé dans le produit
Un réglage que le modèle n’honorait pas
- Observé
- Sur le niveau approfondi en palier qualité, la réflexion du modèle consommait 9 300 à 11 400 jetons alors que son budget était fixé à 6 144. Elle représentait 77 % des jetons de sortie facturés et occupait 80 % du plafond.
- Diagnostic
- Le contrôle chiffré appartient à la génération précédente du modèle ; la génération actuelle le traite comme une préférence, pas comme une borne. Conséquence concrète : une source un peu plus dense tronquait la sortie, et l’utilisateur perdait sa synthèse après avoir été débité.
- Décision
- Parler le vocabulaire que le modèle honore réellement, plutôt que relever le plafond. Relever aurait déplacé le problème sur la marge : au plafond supérieur envisagé, une synthèse coûtait plus cher que ce qu’elle rapportait.
- Résultat
- −31 % de coût sur le palier qualité, et fidélité en hausse.
Une mesure unique mentait
- Observé
- La même configuration, mesurée deux fois de suite, a produit 9 539 puis 3 005 jetons de réflexion — soit 75 % puis 34 % du même budget.
- Diagnostic
- À variance pareille, une mesure unique fait conclure « il faut agir » un jour et « tout va bien » le lendemain. Le risque existait dès la première exécution ; il était seulement invisible.
- Décision
- Chaque cellule de la matrice d’évaluation est répétée, et c’est la pire exécution qui compte. Un seuil d’alerte se fixe sur le pire cas, jamais sur la moyenne.
Le modèle refusait d’écrire aussi long que promis
- Observé
- Sur une source de 150 000 caractères, le niveau le plus complet promettait 2 500 à 4 500 mots et en produisait le quart. Le plafond n’y était pour rien : la génération n’occupait que 2 à 3 % de la place disponible.
- Diagnostic
- Au-delà d’une certaine longueur, le modèle s’arrête, quelle que soit l’insistance de la consigne — y compris quand elle formule l’objectif comme une condition d’échec.
- Décision
- Découper la source en fenêtres et demander une section par fenêtre : un objectif court, que le modèle honore. Chaque fenêtre n’est lue qu’une fois, donc le coût d’entrée reste celui d’une lecture complète — seule la sortie augmente, et c’est exactement ce qu’on veut payer sur le niveau premium.
- Résultat
- Longueur produite multipliée par trois, contrat enfin tenu — et un coût au 95ᵉ centile qui baisse, parce que des sorties courtes sont plus prévisibles qu’une génération unique poussée dans ses retranchements.
Deux prompts renforcés, deux échecs
- Observé
- Le niveau approfondi devait rendre trois à six sections titrées ; le modèle rendait un bloc unique. Première tentative : la consigne formule la structure comme une condition d’échec. Échec. Deuxième : l’instruction est déplacée sur la description du champ lui-même. Échec.
- Diagnostic
- Une consigne ne contraint pas, elle suggère. Aucune reformulation ne transforme une suggestion en garantie.
- Décision
- Rendre la structure impossible à ignorer plutôt que fortement recommandée : le schéma de génération exige un tableau de sections, et le code assemble le rendu final. Le modèle ne peut plus rendre un bloc unique.
- Résultat
- Structure conforme sur la totalité des cas évalués, sans un seul appel supplémentaire — et un test verrouille l’invariant quel que soit le contenu.
Un modèle facturé qui n’existait pas
- Observé
- Le palier qualité échouait en production sur une erreur « ressource introuvable » — après que l’utilisateur ait été débité.
- Diagnostic
- L’identifiant du modèle, repris de la documentation, n’était pas celui que l’API accepte réellement. Un identifiant codé en dur peut aussi être renommé ou retiré sans préavis.
- Décision
- Une commande confronte les identifiants configurés à ceux que la clé ouvre vraiment. La prochaine disparition se verra avant le déploiement, pas sur une action utilisateur déjà payée.
La note que le modèle se donne ne vaut rien
- Observé
- La distillation produisait un score de fidélité… attribué par le modèle qui venait d’écrire le texte.
- Diagnostic
- Structurellement complaisant, et manipulable par injection depuis le contenu ingéré : un contenu hostile peut littéralement demander une note élevée. Ce n’est pas une évaluation, c’est une opinion.
- Décision
- Un juge sur un autre modèle, dont l’écart avec l’auto-évaluation devient l’information intéressante. Mais un juge a lui aussi ses biais — il préfère le texte long et bien écrit — donc ses notes sont confrontées à une notation humaine. Au-delà d’un écart de 0,75 point sur 5, il mesure surtout ses propres préférences : un juge non calibré est un thermomètre non étalonné.
Décisions techniques
Ce qui a été choisi, pourquoi, et ce qui a été écarté
Workflow durable plutôt qu’une file de tâches maison
La reprise à l’étape échouée est native, sans table d’état à maintenir ni logique de rejeu à déboguer.
écarté : File maison + table de statuts
Vecteurs dans Postgres plutôt qu’une base vectorielle dédiée
Une seule base à opérer, à sauvegarder et à sécuriser — et l’isolation par utilisateur se fait dans la même requête que les jointures métier.
écarté : Service vectoriel externe
Recherche hybride plutôt que vectorielle pure
Le vectoriel seul rate les noms propres et les acronymes, précisément ce que l’utilisateur tape quand il cherche quelque chose de précis.
écarté : Similarité vectorielle seule
Schémas partagés entre serveur, client et extension
Une seule définition fait foi : le typage casse à la compilation au lieu de casser en production sur un champ renommé.
écarté : Types dupliqués de chaque côté
Serveur MCP plutôt qu’une API propriétaire
L’utilisateur branche l’assistant qu’il utilise déjà, sans intégration sur mesure à écrire pour chacun.
écarté : API maison + intégrations une par une
Crédits calés sur la consommation mesurée
Le prix suit le coût réel de chaque action au lieu d’une estimation, ce qui rend la marge prévisible dès la première facture.
écarté : Forfait à l’estimation
Observabilité en parallèle plutôt qu’en coupure
Une passerelle s’intercale entre l’application et le modèle : sa panne devient la nôtre, sa latence s’ajoute à chaque appel payant, et le contenu des sources transite chez un tiers. Ici les mesures sont écrites en base d’abord ; l’outil externe n’en est qu’une vue.
écarté : Passerelle en coupure
Prompts dans le dépôt, pas dans un outil dédié
Les récupérer au moment de l’appel ajouterait un aller-réseau et un mode de panne à un chemin payant. Le seul gain — éditer sans déployer — n’a pas de valeur sans équipe non technique à servir. Git versionne, une garde vérifie, l’outil externe reçoit un miroir.
écarté : Gestionnaire de prompts distant
Ce que ce projet démontre
Brancher un modèle de langage prend une après-midi. Ce qui prend du métier, c’est ce qu’il y a autour : reprise sur échec, réponses traçables, schémas qui tiennent, coûts mesurés, données cloisonnées. C’est exactement ce que je conçois pour mes clients — la même rigueur que sur les environnements bancaires, appliquée à des systèmes IA.
Chacune des corrections ci-dessus vient d’une mesure, pas d’une intuition — et deux d’entre elles ont commencé par un échec que l’historique du dépôt conserve. Un système qu’on ne mesure pas ne se dégrade pas moins vite : il se dégrade sans qu’on le sache.
Stack
Vous avez un projet qui ressemble à celui-ci — recherche sur vos propres données, workflows IA, produit à livrer de bout en bout ?