# Epic: Amélioration de la Recherche Sémantique - Version 2.0 **Epic ID:** EPIC-SEARCH-2.0 **Status:** Draft **Priority:** High **Created:** 2026-01-09 **Owner:** Development Team --- ## Description du Problème L'actuel système de recherche hybride (mots-clés + sémantique) produit des résultats non pertinents et imprévisibles. Les utilisateurs se plaignent que la recherche "fait n'importe quoi" - les résultats ne correspondent pas à leurs attentes, même quand les notes contiennent les termes recherchés. ### Analyse des Causes Racines Après analyse du code dans `keep-notes/app/actions/notes.ts` (lignes 108-212), les problèmes identifiés sont: 1. **Seuil de similarité cosine trop bas (0.40)**: Permet des correspondances sémantiques de très faible qualité, créant du bruit dans les résultats 2. **RRF (Reciprocal Rank Fusion) mal configuré**: Le constant k=60 n'est pas optimal pour le petit nombre de notes typique dans Keep 3. **Pondération fixe recherche mot-clé/sémantique**: Les poids sont hardcodés (3 pour titre, 1 pour contenu, 2 pour labels) sans adaptation au type de requête 4. **Absence de validation des embeddings**: Pas de vérification que les embeddings sont correctement générés ou de dimensionnalité cohérente 5. **Manque de prétraitement des requêtes**: Pas de stemming, lemmatization, ou expansion de requête 6. **Aucune métrique de qualité**: Impossible de mesurer l'amélioration ou la dégradation des performances ### Impact Utilisateur - **Frustration**: Perte de temps à filtrer manuellement les résultats non pertinents - **Perte de confiance**: Les utilisateurs désactivent ou évitent la recherche intelligente - **Expérience dégradée**: Contredit la promesse d'une recherche "intuitive" du PRD --- ## Objectifs Mesurables 1. **Améliorer la précision de recherche de 40%** (mesurée par tests automatisés) 2. **Réduire les faux positifs sémantiques de 60%** (seuil plus strict) 3. **Temps de réponse < 300ms** pour 1000 notes (objectif PRD existant) 4. **Satisfaction utilisateur > 4/5** (feedback post-déploiement) 5. **Taux de "serendipity" (résultats sémantiques sans mots-clés) entre 20-40%** (objectif PRD) --- ## User Stories ### Story 1: Validation et Qualité des Embeddings **ID:** SEARCH-2.0-1 **Title:** Valider la qualité des embeddings générés **Priority:** Must Have **Estimation:** 3h **En tant que:** développeur **Je veux:** valider que les embeddings sont correctement générés et stockés **Afin que:** la recherche sémantique fonctionne sur des données de qualité **Critères d'Acceptation:** 1. **Given** une note avec du contenu texte 2. **When** l'embedding est généré via `provider.getEmbeddings()` 3. **Then** le vecteur doit: - Avoir une dimensionnalité > 0 - Contenir des nombres valides (pas de NaN, Infinity) - Avoir une norme L2 normale (entre 0.7 et 1.2) 4. **Given** des embeddings existants en base de données 5. **When** ils sont chargés via `parseNote()` 6. **Then** ils doivent être correctement désérialisés et validés 7. **And** une action admin `/api/debug/embeddings/validate` doit lister les notes problématiques **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Ajouter validation dans `parseNote()` - `keep-notes/lib/utils.ts` - Ajouter `validateEmbedding()` et `normalizeEmbedding()` - `keep-notes/app/api/admin/embeddings/validate/route.ts` - Nouveau endpoint debug **Tests Nécessaires:** - Test unitaire `validateEmbedding()` avec vecteurs valides/invalides - Test d'intégration création note → validation embedding - Test endpoint API debug **Risques:** - Certains embeddings existants invalides nécessiteront un re-indexing - Performance impact de la validation à chaque chargement --- ### Story 2: Optimisation du Seuil de Similarité Sémantique **ID:** SEARCH-2.0-2 **Title:** Ajuster le seuil de similarité cosine pour éliminer le bruit **Priority:** Must Have **Estimation:** 4h **En tant que:** utilisateur **Je veux:** ne voir que des résultats sémantiquement pertinents **Afin que:** la recherche me fasse confiance et me fasse gagner du temps **Critères d'Acceptation:** 1. **Given** une requête de recherche sémantique 2. **When** les notes sont classées par similarité cosine 3. **Then** seules les notes avec similarité >= 0.65 sont considérées (au lieu de 0.40) 4. **And** le seuil doit être configurable dans `SystemConfig` (`SEARCH_SEMANTIC_THRESHOLD`) 5. **Given** une recherche avec résultats sémantiques faibles 6. **When** le seuil est appliqué 7. **Then** les faux positifs sont réduits d'au moins 50% 8. **And** un test automatisé mesure la réduction des faux positifs **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Ligne 190, remplacer 0.40 par `config.SEARCH_SEMANTIC_THRESHOLD || 0.65` - `keep-notes/lib/config.ts` - Lire la config depuis DB - `keep-notes/tests/search-quality.spec.ts` - Ajouter tests de seuil **Tests Nécessaires:** - Test Playwright: recherche "coding" ne retourne PAS des notes sur "cuisine" (faux positifs) - Test unitaire: vérifier que les scores < seuil sont filtrés - Test de non-régression: s'assurer que les vrais positifs ne sont pas perdus **Risques:** - Seuil trop élevé peut éliminer des résultats pertinents (faux négatifs) - Nécessite A/B testing pour trouver le seuil optimal - Différents modèles d'embedding peuvent nécessiter des seuils différents --- ### Story 3: Reconfiguration de l'Algorithme RRF **ID:** SEARCH-2.0-3 **Title:** Optimiser le paramètre k du Reciprocal Rank Fusion **Priority:** Should Have **Estimation:** 3h **En tant que:** système **Je veux:** un RRF avec un paramètre k adapté au nombre de notes typique **Afin que:** le ranking hybride reflète mieux la pertinence réelle **Critères d'Acceptation:** 1. **Given** un RRF avec constant k 2. **When** le nombre moyen de notes par utilisateur est < 500 3. **Then** k doit être 20 (au lieu de 60) pour mieux pénaliser les bas rangs 4. **And** k doit être configurable: `k = max(20, nombre_notes / 10)` 5. **Given** deux listes de ranking (mot-clé + sémantique) 6. **When** RRF est appliqué 7. **Then** les résultats bien classés dans les deux listes sont fortement favorisés 8. **And** la formule RRF est documentée dans le code **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Lignes 182-198, ajuster k et ajouter logique adaptive - `keep-notes/lib/utils.ts` - Ajouter `calculateRRFK(totalNotes: number): number` **Tests Nécessaires:** - Test unitaire: vérifier la formule RRF avec différents k - Test d'intégration: comparer rankings avec k=20 vs k=60 - Test avec dataset de benchmark (notes + requêtes + résultats attendus) **Risques:** - Changer k peut impacter significativement l'ordre des résultats - Nécessite validation utilisateur sur de vraies données - Peut nécessiter des ajustements itératifs --- ### Story 4: Pondération Adaptative des Scores de Recherche **ID:** SEARCH-2.0-4 **Title:** Adapter les poids mot-clé/sémantique selon le type de requête **Priority:** Should Have **Estimation:** 6h **En tant que:** utilisateur **Je veux:** que la recherche privilégie les mots-clés pour les termes exacts **Et qu'elle privilégie le sémantique pour les concepts abstraits **Afin que:** les résultats soient toujours pertinents **Critères d'Acceptation:** 1. **Given** une requête avec des guillemets (ex: `"Error 404"`) 2. **When** la recherche est exécutée 3. **Then** le poids mot-clé est multiplié par 2 (recherche exacte prioritaire) 4. **Given** une requête conceptuelle (ex: "comment améliorer...") 5. **When** la recherche est exécutée 6. **Then** le poids sémantique est multiplié par 1.5 (concept prioritaire) 7. **Given** une requête mixte 8. **When** aucun pattern n'est détecté 9. **Then** les poids par défaut sont utilisés 10. **And** la logique de détection est documentée **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Ajouter `detectQueryType()` et ajuster les poids - `keep-notes/lib/types.ts` - Ajouter `QueryType: 'exact' | 'conceptual' | 'mixed'` - `keep-notes/lib/utils.ts` - Ajouter `detectQueryType(query: string): QueryType` **Tests Nécessaires:** - Test unitaire `detectQueryType()` avec différents patterns - Test d'intégration: vérifier que `"Error 404"` privilégie les mots-clés - Test d'intégration: vérifier que "comment cuisiner" privilégie le sémantique - Test Playwright: scénarios de recherche avec guillemets **Risques:** - La détection automatique du type de requête peut être imprécise - Nécessite des règles bien pensées pour éviter les effets de bord - Peut nécessiter du machine learning pour être vraiment efficace --- ### Story 5: Expansion et Normalisation des Requêtes **ID:** SEARCH-2.0-5 **Title:** Améliorer les requêtes par expansion et normalisation **Priority:** Could Have **Estimation:** 5h **En tant que:** utilisateur francophone/anglophone **Je veux:** que ma recherche trouve les résultats même avec des variations de mots **Afin que:** je n'aie pas à deviner les termes exacts utilisés **Critères d'Acceptation:** 1. **Given** une requête avec des mots au pluriel (ex: "recettes pizzas") 2. **When** la recherche est exécutée 3. **Then** les singuliers sont aussi recherchés ("recette pizza") 4. **Given** une requête avec des accents (ex: "éléphant") 5. **When** la recherche est exécutée 6. **Then** les variantes sans accents sont aussi recherchées ("elephant") 7. **Given** une requête courte (< 3 mots) 8. **When** l'expansion est activée 9. **Then** des synonymes courants sont ajoutés (ex: "bug" → "erreur", "problème") 10. **And** l'expansion est limitée à 3 termes par mot original **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Ajouter `expandQuery()` avant le calcul des scores - `keep-notes/lib/utils.ts` - Implémenter `expandQuery()` et `normalizeText()` - `keep-notes/lib/data/synonyms.json` - Créer une liste de synonymes (FR/EN) **Tests Nécessaires:** - Test unitaire `expandQuery()` avec différents cas - Test d'intégration: recherche "pizzas" trouve notes avec "pizza" - Test d'intégration: recherche "bug" trouve notes avec "erreur" - Test performance: vérifier que l'expansion ne dégrade pas les performances **Risques:** - L'expansion de requête peut augmenter significativement les faux positifs - La gestion des synonymes est complexe et contextuelle - Nécessite une base de synonymes bien maintenue - Peut ne pas être pertinent pour toutes les langues --- ### Story 6: Interface de Debug et Monitoring de Recherche **ID:** SEARCH-2.0-6 **Title:** Créer une interface de debug pour analyser la qualité de recherche **Priority:** Should Have **Estimation:** 8h **En tant que:** développeur/testeur **Je veux:** visualiser les détails du calcul de score pour chaque résultat **Afin que:** je puisse comprendre et optimiser la recherche **Critères d'Acceptation:** 1. **Given** une recherche exécutée 2. **When** le mode debug est activé (`?debug=true`) 3. **Then** chaque résultat affiche: - Score mot-clé brut - Score sémantique brut (similarité cosine) - Score RRF final - Rang dans chaque liste (mot-clé / sémantique) 4. **Given** la page `/debug-search` 5. **When** j'accède à la page 6. **Then** je vois une interface pour: - Tester des requêtes avec tous les paramètres - Comparer différents seuils de similarité - Voir les embeddings des notes 7. **And** les métriques sont exportables en JSON **Fichiers à Modifier:** - `keep-notes/app/actions/notes.ts` - Retourner les scores debug si demandé - `keep-notes/app/debug-search/page.tsx` - Créer la page (existante dans git status) - `keep-notes/components/search-debug-results.tsx` - Nouveau composant - `keep-notes/app/api/debug/search/route.ts` - Nouveau endpoint API **Tests Nécessaires:** - Test E2E: accès à la page debug-search - Test API: endpoint retourne bien les scores détaillés - Test visuel: vérifier l'affichage des scores dans l'UI - Test de performance: vérifier que le mode debug n'impacte pas la recherche normale **Risques:** - Complexité supplémentaire dans la UI - Nécessite de bien sécuriser l'accès (admin uniquement) - Informations sensibles (embeddings) visibles --- ### Story 7: Re-génération et Validation des Embeddings Existants **ID:** SEARCH-2.0-7 **Title:** Script de re-indexation des embeddings invalides ou manquants **Priority:** Must Have **Estimation:** 4h **En tant que:** administrateur système **Je veux:** un script pour régénérer les embeddings des notes existantes **Afin que:** toutes les notes bénéficient de la recherche sémantique **Critères d'Acceptation:** 1. **Given** des notes avec embeddings manquants ou invalides 2. **When** je lance le script `npm run reindex-embeddings` 3. **Then** les embeddings sont régénérés pour toutes les notes 4. **And** la progression est affichée (X/Y notes traitées) 5. **Given** des notes avec des embeddings valides 6. **When** le script est lancé avec `--force` 7. **Then** tous les embeddings sont régénérés (même les valides) 8. **And** le script peut être relancé sans erreurs (idempotent) 9. **And** un rapport final résume les succès/erreurs **Fichiers à Modifier:** - `keep-notes/scripts/reindex-embeddings.ts` - Créer le script - `keep-notes/app/actions/admin.ts` - Ajouter `reindexAllEmbeddings()` - `keep-notes/package.json` - Ajouter le script npm **Tests Nécessaires:** - Test du script sur base de données vide - Test du script avec des notes sans embeddings - Test du script avec des embeddings invalides (NaN, dimension 0) - Test du mode `--force` - Test d'idempotence (relancer le script deux fois) **Risques:** - Temps d'exécution long si beaucoup de notes (plusieurs minutes/heures) - Peut saturer le provider IA (Ollama/OpenAI) avec trop de requêtes - Nécessite un mécanisme de rate limiting - Peut impacter les performances de l'application si lancé pendant l'utilisation --- ### Story 8: Suite de Tests Automatisés de Qualité de Recherche **ID:** SEARCH-2.0-8 **Title:** Créer des tests automatisés pour mesurer la qualité de recherche **Priority:** Should Have **Estimation:** 6h **En tant que:** développeur **Je veux:** une suite de tests automatisés pour valider la qualité de recherche **Afin que:** les améliorations soient mesurables et les régressions détectées **Critères d'Acceptation:** 1. **Given** un dataset de test (notes + requêtes + résultats attendus) 2. **When** les tests sont exécutés 3. **Then** les métriques suivantes sont calculées: - Precision: % de résultats pertinents dans le top 10 - Recall: % de résultats pertinents trouvés - MRR (Mean Reciprocal Rank): rang moyen du premier résultat pertinent - Faux positifs sémantiques: résultats sans pertinence 4. **Given** une modification du code de recherche 5. **When** les tests sont relancés 6. **Then** une régression de plus de 5% fait échouer les tests 7. **And** les tests prennent < 2 minutes à s'exécuter 8. **And** un rapport HTML est généré pour analyse **Fichiers à Modifier:** - `keep-notes/tests/search-benchmark.spec.ts` - Créer le benchmark - `keep-notes/tests/fixtures/search-dataset.json` - Dataset de test - `keep-notes/tests/utils/search-metrics.ts` - Utilitaires de calcul de métriques - `keep-notes/playwright.config.ts` - Configuration pour générer le rapport HTML **Tests Nécessaires:** - Test du test (métatest) avec un dataset trivial - Test avec dataset réel (notes sur tech, cuisine, voyage, etc.) - Test de régression: introduire un bug et vérifier que les tests le détectent - Test de performance: temps d'exécution des tests **Risques:** - Création du dataset manuelle et longue - Subjectivité de la "pertinence" (qui décide quoi est pertinent?) - Maintenance du dataset à chaque nouvelle feature - Tests peuvent être "flaky" si les embeddings changent --- ## Dépendances Entre Stories ``` SEARCH-2.0-1 (Validation Embeddings) ↓ SEARCH-2.0-7 (Re-génération) ← dépend de 1 pour détecter les invalides ↓ SEARCH-2.0-2 (Seuil Similarité) ← dépend de 1 pour des embeddings valides ↓ SEARCH-2.0-3 (RRF Config) ↓ SEARCH-2.0-4 (Pondération Adaptative) ↓ SEARCH-2.0-8 (Tests Automatisés) ← dépend de 2,3,4 pour mesurer les améliorations ↓ SEARCH-2.0-6 (Debug Interface) ← utile pendant le développement de toutes les autres ↓ SEARCH-2.0-5 (Expansion Requêtes) ← amélioration optionnelle à la fin ``` **Ordre recommandé:** 1. **Sprint 1:** SEARCH-2.0-1, SEARCH-2.0-7 (Base solide avec embeddings valides) 2. **Sprint 2:** SEARCH-2.0-2, SEARCH-2.0-3 (Corrections critiques du ranking) 3. **Sprint 3:** SEARCH-2.0-4, SEARCH-2.0-6 (Améliorations + tooling) 4. **Sprint 4:** SEARCH-2.0-8, SEARCH-2.0-5 (Tests + optimisations optionnelles) --- ## Métriques de Succès ### Avant/Après (Objectifs) | Métrique | Avant | Après (Objectif) | Comment Mesurer | |----------|-------|------------------|-----------------| | Précision Top-10 | ~50% (estimé) | 70%+ | Tests automatisés Story 8 | | Faux positifs sémantiques | ~30% | <10% | Tests Playwright | | Temps de réponse (1000 notes) | 200ms | <300ms | Tests performance | | Taux de serendipité | N/A | 20-40% | Tests dataset | | Satisfaction utilisateur | 2/5 (subjectif) | 4/5+ | Sondage post-déploiement | --- ## Configuration Système Proposée Nouveaux paramètres dans `SystemConfig`: ```typescript interface SearchConfig { // Seuil de similarité cosine minimum (0-1) SEARCH_SEMANTIC_THRESHOLD: number; // défaut: 0.65 // Constante k pour RRF (adaptive) SEARCH_RRF_K_BASE: number; // défaut: 20 SEARCH_RRF_K_ADAPTIVE: boolean; // défaut: true // Pondération mot-clé vs sémantique SEARCH_KEYWORD_BOOST_EXACT: number; // défaut: 2.0 (guillemets) SEARCH_KEYWORD_BOOST_CONCEPTUAL: number; // défaut: 0.7 SEARCH_SEMANTIC_BOOST_EXACT: number; // défaut: 0.7 SEARCH_SEMANTIC_BOOST_CONCEPTUAL: number; // défaut: 1.5 // Expansion de requête SEARCH_QUERY_EXPANSION_ENABLED: boolean; // défaut: true SEARCH_QUERY_EXPANSION_MAX_SYNONYMS: number; // défaut: 3 // Debug SEARCH_DEBUG_MODE: boolean; // défaut: false } ``` --- ## Fichers Critiques pour l'Implémentation Les 5 fichiers les plus importants à modifier: 1. **keep-notes/app/actions/notes.ts** - Logique de recherche principale (RRF, seuils, ranking) 2. **keep-notes/lib/utils.ts** - Fonctions de similarité cosine et nouvelles utilités 3. **keep-notes/lib/ai/providers/ollama.ts** - Génération des embeddings avec validation 4. **keep-notes/tests/search-quality.spec.ts** - Tests de qualité de recherche 5. **keep-notes/lib/config.ts** - Configuration des nouveaux paramètres --- **Document Version:** 1.0 **Last Updated:** 2026-01-09 **Agent:** Plan (a551c9b)