← Retour à l'accueil

Comment ça marche

Seek A Story est un moteur de recommandation de livres. Décrivez ce que vous avez envie de lire — un thème, une ambiance, « comme X mais en plus énervé » — et il cherche parmi environ 900 000 livres réels, puis renvoie une courte sélection variée, chaque titre accompagné de sa couverture et d'une phrase expliquant pourquoi il correspond.


La version technique

Le cœur de la recommandation est un système de recherche par contenu : les livres proviennent d'un vrai catalogue, et non du souvenir qu'un modèle de langage a de ce qu'un livre intitulé 1984 raconte probablement.

Un vrai catalogue, sans hallucinations

Environ 900 000 livres en anglais importés des exports mensuels d'Open Library, filtrés sur la présence d'une vraie description et d'au moins un sujet, puis normalisés dans Postgres. Chaque livre que l'application peut recommander existe réellement : les titres ne peuvent plus être inventés.

Une recherche sémantique qui tient sur une petite machine

Les livres sont vectorisés (bge-small-en-v1.5 via ONNX/fastembed, sans PyTorch) dans Qdrant avec une quantification scalaire int8, si bien que l'index HNSW de ~900 000 vecteurs n'occupe qu'une fraction de gigaoctet de RAM. Le catalogue est en plus enrichi de descripteurs générés par LLM, afin d'affiner la recherche sur les requêtes vagues ou fondées sur une ambiance, que les seules quatrièmes de couverture captent mal.

Le LLM comprend la requête, il ne recommande pas

Un appel de compréhension de requête transforme le texte libre en une description prête à être vectorisée, accompagnée de filtres structurés et de termes d'ancrage. Le rôle du modèle s'arrête à l'interprétation de la requête ; il ne choisit jamais un livre lui-même. Les candidats retrouvés sont ensuite fusionnés par reciprocal rank fusion entre les signaux vectoriels et les descripteurs, avec une pénalité d'auteur déjà retenu et une diversification de type MMR, pour éviter cinq titres quasi identiques du même auteur.

Des explications ancrées

Un dernier appel LLM groupé rédige une phrase par livre, en s'appuyant uniquement sur les sujets qui ont matché et sur les signaux extraits de la requête. Il ne peut pas inventer de faits sur un livre qu'il n'a pas « lu ».

Piloté par l'évaluation

Chaque décision de classement ou de modèle — modèle d'embedding, pondération hybride, reranking — vient de l'exécution d'un jeu d'évaluation d'une centaine de requêtes dans un harnais de jugement par LLM, avec comparaison des scores, plutôt que de l'observation de quelques exemples.

Un déploiement sous contrainte de ressources

Le recommandeur tourne comme son propre microservice FastAPI sur un petit VPS partagé, auto-hébergé derrière nginx avec TLS et un jeton bearer. La quantification, le runtime ONNX mono-thread, les limites cgroup de mémoire et de CPU : tout est réglé pour que le service survive aux côtés d'autres conteneurs sans rapport, sur environ 3 Go de RAM.

Stack technique

Frontend
Next.js (React 19)
Backend
Python / FastAPI (orchestration des requêtes) + un service de recommandation Python / FastAPI distinct
Base vectorielle
Qdrant (auto-hébergé, quantifié en int8)
Stockage des métadonnées
PostgreSQL
Embeddings
fastembed / ONNX Runtime (bge-small-en-v1.5)
LLM
API OpenAI (compréhension des requêtes, enrichissement des descripteurs et explications ancrées)
Déploiement
Vercel (frontend + backend) et un VPS Hetzner auto-hébergé (recommandeur, via Docker + nginx)