ADR-03, Pipeline de recherche
Pourquoi 3 couches successives plutôt qu'un seul appel LLM
Statut : Accepté Date : 2026-04 Sujet : Architecture du moteur de recherche de membres
Contexte
La base peut contenir jusqu'à 70 000 membres. Pour chaque demande, on doit identifier les 8-12 meilleurs candidats en quelques secondes. L'IA doit pouvoir expliquer ses choix.
Le défi : on ne peut pas envoyer 70 000 fiches à Claude. Limites d'API, coûts prohibitifs, latence inacceptable.
Alternatives considérées
Option 1, "Tout LLM"
Envoyer toutes les fiches à Claude et lui demander de trier.
Pour : ultra-flexible, l'IA voit tout
Contre :
- Impossible : 70k fiches × ~500 tokens = 35M tokens d'input. Au-dessus des limites de contexte
- Coût : ~50€ par recherche
- Latence : > 2 minutes
- Pas viable
Option 2, "Tout filtre SQL"
Filtrage purement technique, ranking par règles fixes.
Pour : ultra-rapide, prédictible, gratuit
Contre :
- Pas de compréhension sémantique
- Ne capte pas les nuances (ambiance, type de clientèle, contraintes implicites)
- Tous les utilisateurs Reciprok auraient les mêmes résultats, pas d'apprentissage
Option 3, Pipeline 3 couches (retenu)
Filtrage SQL → Recherche vectorielle → Ranking LLM.
Décision
Total cible : moins de 5 secondes end-to-end.
Étape 1, SQL hard filtering
Filtres "durs" sur les contraintes techniques :
- Géographie (code postal, rayon GPS)
- Capacité minimum (selon le format de l'événement)
- Budget compatible (fourchette du membre vs budget de la demande)
- Disponibilité aux dates demandées
- Statut actif
Index Postgres optimisés :
- Index composite sur
(postal_code, status) - Index GIST géospatial pour la recherche par rayon
- Index sur
(member_id, date)pour le join des disponibilités
Garantie : élimine 99% de la base en < 50ms.
Étape 2, Recherche vectorielle
Sur les ~500 candidats restants, classement par similarité sémantique.
L'embedding d'un membre est généré à partir de :
- Sa description
- Ses tags (concaténés)
- Le contenu de ses
KnowledgeEntry(audio transcrit + structuré) - Les noms et descriptions de ses rooms
Régénéré quand un de ces champs change (worker async).
L'embedding de la demande est généré à partir de :
- Sa description
- Ses informations complémentaires
- Le format, le nombre de personnes, le budget, la zone
Recherche pgvector :
SELECT m.id, m.code, 1 - (m.embedding <=> $1::vector) AS similarity
FROM member m
WHERE m.id = ANY($2)
ORDER BY m.embedding <=> $1::vector
LIMIT 50;<=> = distance cosinus pgvector.
Garantie : ranking sémantique en < 300ms.
Étape 3, LLM ranking
Sur les 20-50 finalistes, on demande à Claude de :
- Sélectionner les 8 meilleurs (4 recommandés + 4 compatibles)
- Expliquer en une phrase pourquoi chaque membre est pertinent
- Éliminer ceux qui paraissent inappropriés malgré le score vectoriel
- Comparer avec des demandes passées similaires (via vector search sur l'historique)
Format de prompt :
Demande : [résumé structuré]
Candidats : [liste de 30 membres avec leurs infos clés et extraits KB]
Tâche :
1. Sélectionne les 4 fortement recommandés et les 4 compatibles
2. Justifie chaque choix en une phrase (pourquoi CETTE demande)
3. Si un candidat te paraît mauvais malgré son score, dis-le
Réponse JSON :
{
"recommended": [{ memberCode, explanation }],
"compatible": [{ memberCode, explanation }],
"rejected": [{ memberCode, reason }]
}Garantie : ranking expliqué en 2-3 secondes.
Cas limites
Demande très large
Si l'organisateur dit "n'importe où en Île-de-France", l'étape 1 ne réduit presque rien.
Stratégie : l'IA propose à l'utilisateur de préciser, ou on prend les 500 plus proches géographiquement de Paris-centre.
Demande très précise
Si l'utilisateur demande explicitement #102 et #157, on saute le pipeline et on sert directement.
Demande similaire à une demande passée
L'IA peut réutiliser un catalogue passé sans relancer le pipeline complet (avec accord de l'utilisateur). C'est une optimisation de l'étape 3 via le tool find_similar_requests.
Métriques à monitorer
- Latence p50/p95/p99 par étape
- Taux de candidats par étape (entonnoir : 70k → 500 → 50 → 8)
- Taux d'acceptation des suggestions IA (corrigées vs gardées)
- Coût par recherche (tokens LLM + appels embedding)
Conséquences
Positives :
- Performance prédictible et tenable
- Coût maîtrisé (l'IA ne voit que 50 fiches max)
- Explicabilité : l'IA justifie chaque choix
- Évolutif : on peut affiner chaque étape indépendamment
Négatives :
- 3 systèmes à maintenir (SQL, vector, LLM)
- Si l'étape 1 est trop laxiste ou trop stricte, ça pollue les étapes suivantes
- L'embedding d'un membre doit être à jour (worker à monitorer)