Un lycéen envoie une question à plusieurs éclaireurs en une fois, avec reformulation IA optionnelle et matching automatique via Elasticsearch. Schéma des tables multicontacts / ai-agents : voir entities/multicontact.md.
Flux
Deux points d'entrée, qui convergent vers le même traitement :
- Avec reformulation IA (par défaut) :
POST /multicontact/rephraseappelle l'agent IA pour reformuler la question et crée la ligne tout de suite avecreformulationStatus = abandoned. PuisPOST /multicontact/send/generatedmet à jour le statut enacceptedoumodified(calculé côté front en comparant le texte édité au texte reformulé) et lance le matching. - Sans reformulation :
POST /multicontact/send/originalcrée directement la ligne enoriginal, choisit un agent actif au hasard (uniquement pour générer la requête Elasticsearch, pas pour reformuler le texte), et lance le matching.
Matching (commun aux deux flux) :
- L'agent IA génère un JSON de paramètres de recherche à partir de la question + du profil du lycéen.
typeest forcé àeclaireuretlimit/pageforcés à3/1, quoi que l'IA ait proposé.- La requête Elasticsearch et ses résultats (
elasticQuery/elasticResults) sont sauvegardés avant de vérifier s'il y a des résultats — une recherche à zéro résultat laisse donc une trace exploitable. - Zéro résultat → erreur visible côté lycéen ("essaie une demande moins spécifique"). Sinon, un canal de chat est créé avec chaque éclaireur trouvé (ou un message est posté dans un canal existant s'il y en a déjà un), et
channelId1/2/3sont renseignés.
Le "3" est en dur, pas configurable
- La limite de recherche (3) est forcée dans le code à plusieurs endroits, indépendamment de ce que l'IA renvoie.
- Le schéma lui-même n'a que 3 colonnes
channelId1/2/3(pas de table de liaison) — la limite est structurelle, pas juste applicative. - Moins de 3 éclaireurs trouvés : les colonnes restantes sont
null, la requête réussit quand même. Seul le cas 0 résultat échoue.
reformulationStatus
abandonedest la valeur par défaut posée à la création de la ligne dansrephrase()— ce n'est pas un événement détecté après coup. Aucun cron ni timeout ne bascule une ligne versabandoned: si le lycéen ferme la modale sans envoyer, la ligne resteabandonedindéfiniment par défaut.- Le bouton "Envoyer ma question originale" reste toujours disponible, même après avoir vu la reformulation IA — la reformulation ne conditionne jamais l'envoi.
Pièges IA
- Si le JSON de requête Elasticsearch renvoyé par le LLM n'est pas parsable, le code dégrade silencieusement vers une recherche texte simple (
q = userQuestion) plutôt que d'échouer. OPENAI_API_KEYetMISTRAL_API_KEYsont tous les deux obligatoires au démarrage de l'application (les deux clients sont instanciés sans condition), même si un seul provider est réellement utilisé par les agents actifs.- Aucun retry, backoff ni suivi de quota : chaque envoi déclenche 2 appels LLM synchrones (reformulation + génération de requête ES), sans cache.
Décalage d'indexation Elasticsearch
Réindexation incrémentale toutes les minutes, complète chaque nuit (voir cron-tasks.md). Un éclaireur qui vient de compléter son profil peut ne pas être matchable pendant ~1 minute ; à l'inverse, un profil ayant perdu son parcours éclaireur peut encore ressortir jusqu'à la prochaine passe — dans ce cas la création du canal échoue pour cet éclaireur précis, l'erreur est loguée et avalée (pas remontée), et le nombre de canaux créés est simplement plus petit que prévu, sans avertissement.
Admin
- Le bouton "Tester la reformulation" (fiche agent) appelle le vrai endpoint de production — il crée une véritable ligne
multicontacts(statutabandoned), même utilisé depuis l'admin. - Le bouton "Tester la recherche d'Éclaireurs" est lui sans effet de bord (pas de ligne créée).
- Seuls les agents
status = activepeuvent être sélectionnés au hasard pour un vrai lycéen ;draft/archivene sont atteignables qu'en passant un id explicite (l'admin).
Pièges front / documentation
- Le toast de succès annonce toujours "3 étudiants Éclaireurs", quel que soit le nombre réel de canaux créés — le composant qui affiche le vrai compte (
MulticontactSuccess.vue) existe mais n'est jamais utilisé dans l'app. - La collection Bruno (
bruno/Multicontact/,bruno/Admin - AI Agents/) est obsolète par rapport aux DTOs actuels (champseclaireurId,eclaireurIds,systemPrompt,lyceenId/pisteSlugqui n'existent plus) — ne pas s'y fier comme référence d'API à jour.