La plupart des barres de recherche existent pour répondre à une question : qu'est-ce qui correspond aux mots que j'ai tapés ? C'est la bonne question pour un catalogue de bibliothèque. C'est la mauvaise question pour une application de nutrition.
Lorsqu'un utilisateur ouvre la page des recettes dans Appetec, les mots qu'il tape sont la partie la moins intéressante de la requête. Ce qui compte réellement est invisible : les calories restantes pour aujourd'hui, ce qu'il a cuisiné semaine après semaine sans y penser, s'il est vegan, et s'il a déjà dépassé son budget avant le dîner. Un moteur de recherche qui ignore tout cela retournera avec plaisir des recettes correspondantes — et sabotera tout aussi volontiers la raison pour laquelle l'utilisateur a ouvert l'application. Voici l'architecture derrière un modèle conçu pour combler cet écart : une pertinence adaptée à cette personne, maintenant.
Pourquoi la correspondance textuelle seule ne suffit pas
Ce modèle s'applique chaque fois que la même requête devrait renvoyer des résultats différents pour différents utilisateurs — lorsque le "meilleur" dépend d'un contexte que l'utilisateur n'a jamais saisi, et lorsque le catalogue que vous possédez est plus petit que celui que vos utilisateurs attendent. La recherche de recettes personnalisée traite la pertinence comme un mélange de trois signaux au lieu d'un seul :
| Signal | Ce qu'il capture |
|---|---|
| Texte | Ce que l'utilisateur a tapé — ou, en mode scan de repas, ce qui a été détecté dans son assiette |
| Adéquation à l'objectif | Dans quelle mesure une recette correspond aux calories qu'il reste à l'utilisateur pour ce repas, aujourd'hui |
| Habitude | La fréquence à laquelle cet utilisateur spécifique mange réellement cette recette |
Un moteur naïf ne voit que la première ligne de ce tableau. Ce modèle fusionne les trois en une seule liste classée, puis remplit à partir d'une source externe chaque fois que le catalogue local s'épuise — enrichissant discrètement ce catalogue à chaque fois. Le résultat ressemble moins à l'interrogation d'une base de données et plus à un ami qui connaît déjà votre cuisine et votre régime.
Comment le pipeline est assemblé
Le système fonctionne comme un pipeline de récupération avec une couche de personnalisation en amont et un catalogue auto-réparateur en aval.
Deux moteurs de recherche, un seul contrat. Elasticsearch est le moteur principal : tolérance aux fautes de frappe approximatives, racinisation anglaise ("grill" trouve "grilled"), score de pertinence pondéré. S'il n'est pas disponible, la même requête est transmise à une recherche MongoDB respectant les mêmes filtres et règles de personnalisation. La recherche se dégrade en cas de défaillance ; elle ne disparaît jamais.
La couche que les utilisateurs ressentent mais ne voient jamais. Avant le retour des résultats, trois éléments remodèlent la liste. Le budget calorique est calculé à partir de l'objectif quotidien moins ce qui a été enregistré, favorisant les recettes qui correspondent à la fenêtre restante — et si l'utilisateur a dépassé son objectif, la liste se réordonne discrètement par les recettes les moins caloriques en premier, encourageant la récupération plutôt que l'indulgence. L'historique alimentaire distille les 60 derniers jours de journaux de repas en une carte "ce que vous mangez réellement", gardant les favoris à portée de main en un seul tap sur la première page. La préférence alimentaire — veg, vegan, non-veg, plus deux douzaines de balises plus fines comme keto, gluten-free et high-protein — filtre et reclasse tout le reste.
Un catalogue qui s'auto-développe. Lorsque les résultats locaux s'épuisent, le pipeline bascule vers une source externe, FatSecret, intégrant ces résultats dans le même flux à défilement infini sans aucune discontinuité visible. Il réécrit ensuite ces recettes externes dans le catalogue local, en utilisant un LLM pour classer chacune comme veg, non-veg ou vegan, afin qu'elle puisse être correctement personnalisée la prochaine fois — le même instinct derrière une architecture de récupération hybride que nous avons appliquée ailleurs, où les résultats locaux et externes doivent donner l'impression de provenir d'un seul système.
Trois caches, trois tâches. Les ensembles de résultats combinés, la carte d'habitudes par utilisateur et les réponses des API externes ont chacun leur propre durée de vie indépendante, de sorte qu'une requête personnalisée multi-sources retourne tout de même les résultats à peu près à la vitesse d'une simple recherche.
Les compromis dignes d'être nommés
Rien de tout cela n'a été gratuit, et être honnête sur le coût fait partie de ce qui rend ce modèle fiable plutôt que simplement astucieux.
La pertinence a été délibérément rendue non neutre. Un classement basé sur la pertinence textuelle pure est "juste" d'une manière qui est activement inutile ici. Les résultats sont volontairement biaisés en faveur de l'adéquation à l'objectif et de l'habitude, car une recette légèrement moins parfaite textuellement mais qui correspond au budget et aux goûts de quelqu'un est une meilleure réponse. "Pourquoi cela est-il classé en premier ?" devient une décision produit, et non une valeur par défaut du moteur de recherche — c'est pourquoi ces poids résident dans le code de l'application, et non dans un fichier de configuration non géré.
Posséder l'intégralité du catalogue n'était pas l'objectif — posséder la bonne partie l'était. Les extrêmes considérés étaient un catalogue entièrement curaté (haute qualité, portée étroite) ou un proxy complet vers une API externe (portée illimitée, contrôle nul, personnalisation nulle). Le système fait la différence : il privilégie les recettes possédées et créées par l'utilisateur, remplit en externe si nécessaire, et absorbe ce qui est rempli — tendant, avec le temps, à posséder exactement ce que les utilisateurs recherchent réellement.
La résilience a été préférée à l'efficacité maximale. Deux moteurs de recherche complets coûtent plus cher à exploiter qu'un seul — la même philosophie axée sur la résilience derrière la plupart de nos décisions backend. Ce coût a été délibérément accepté : la recherche de recettes est une fonctionnalité essentielle, d'usage quotidien, où une recherche dégradée est un désagrément mineur, mais une recherche manquante rend l'application inutilisable.
La personnalisation coûte en prévisibilité. Les résultats diffèrent réellement selon l'heure de la journée, les calories consommées et l'historique — même pour le même utilisateur au petit-déjeuner par rapport au dîner. C'est voulu, mais cela élève le niveau pour les tests et le support : "ça marche sur mon téléphone" perd beaucoup de son sens lorsque la justesse est définie par utilisateur et par moment.
Quand ce modèle convient — et quand il ne convient pas
Il est important de clarifier les cas où ce modèle ne devrait pas être utilisé. Sa complexité est justifiée lorsque les résultats doivent réellement différer par utilisateur en fonction d'un contexte qu'ils n'ont jamais saisi, lorsque le catalogue détenu est plus petit que ce que les utilisateurs attendent mais peut être enrichi en externe, lorsque la fonctionnalité est suffisamment fréquente pour que la dégradation gracieuse importe plus qu'une infrastructure minimale, et lorsque la "pertinence" inclut légitimement des signaux au-delà du texte.
C'est le mauvais outil lorsque les résultats sont identiques pour chaque utilisateur — une recherche de documentation publique, une recherche de SKU — où la personnalisation ajoute des coûts sans bénéfice. C'est inutile lorsque le catalogue est suffisamment petit et stable pour qu'un seul moteur avec un filtrage simple suffise, et cela va à l'encontre de vos objectifs partout où un classement strict, identique et entièrement explicable est une exigence absolue.
Le vrai travail
La qualité de la récupération est ici traitée comme un problème de personnalisation, et pas seulement comme un problème de correspondance. Une recherche de recette textuellement parfaite mais ignorant que l'utilisateur est vegan, a déjà dépassé son budget et a cuisiné les cinq mêmes dîners tout le mois a techniquement fonctionné mais a pratiquement échoué. Le but n'a jamais été de trouver des recettes qui correspondent à la requête — c'est de faire remonter la recette que cette personne devrait cuisiner ensuite. Chaque couche de cette architecture, des moteurs de recherche doubles au réordonnancement discret lorsque quelqu'un dépasse son budget, existe pour combler cet écart.
Si vous envisagez une décision similaire de "construire ou mélanger" pour la recherche ou la récupération dans votre propre produit, nous serions ravis d'en discuter.
Autres articles de blog
1. Mettre à l'échelle une plateforme de santé numérique avec des Microservices
2. Synchroniser Apple Health & Health Connect
3. Optimiser le logo de la chaîne pour différentes résolutions vidéo

