Reciprok Docs

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 :

  1. Sélectionner les 8 meilleurs (4 recommandés + 4 compatibles)
  2. Expliquer en une phrase pourquoi chaque membre est pertinent
  3. Éliminer ceux qui paraissent inappropriés malgré le score vectoriel
  4. 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)

On this page