Skip to main content

Chatbot — moteur RAG + Ollama

backend/src/services/rag.service.js + chatbot.service.js + chatbot.controller.js + knowledge.controller.js + frontend/src/components/ChatbotWidget.vue + frontend/src/views/backoffice/KnowledgeManagerView.vue.

Dernière vérification contre le code

Vérifié le 2026-08-13.

Le chatbot est actuellement désactivé côté serveur — choix assumé, pas un bug

Dans backend/server.js, le montage de chatbotRoutes sur /api/chatbot et l'initialisation de chatbotService au démarrage sont commentés. POST /api/chatbot/ask — pourtant appelé par ChatbotWidget.vue, qui lui reste actif dans l'UI — répond donc 404 en l'état de ce dépôt. C'est volontaire : le chatbot n'est pas encore mis en service, ce n'est pas une régression à corriger en urgence. Vérifier l'état de server.js avant de supposer que le chatbot fonctionne, et coordonner avec l'équipe avant de le réactiver (Ollama doit être accessible avec les modèles nomic-embed-text/qwen2.5:1.5b déjà tirés, sinon le serveur logue des échecs en boucle au démarrage).

1. Deux moteurs NLP coexistent — un seul est réellement branché

Le message affiché au démarrage du serveur ("NLP Model Trained Successfully with 26 intents", voir README backend) correspond à un ancien moteur (@nlpjs/basic + @nlpjs/lang-fr), avec des intents statiques dans backend/src/data/chatbot-training.json (26 entrées {intent, utterances, answer}) et un modèle compilé model.nlp à la racine du dépôt. Rien dans le code actuel ne charge ni n'entraîne ce fichier — aucune référence à NlpManager/@nlpjs dans src/.

Le moteur réellement actif est un système RAG (Retrieval-Augmented Generation) appuyé sur Ollama, entièrement différent, décrit ci-dessous. Le message de démarrage du README est trompeur — il documente un système qui n'est plus celui qui répond aux utilisateurs.

2. Architecture RAG (rag.service.js)

  • Embeddings : Ollama, modèle nomic-embed-text. Génération de texte : Ollama, modèle qwen2.5:1.5b. Hôte/modèles configurables via OLLAMA_HOST/OLLAMA_TEXT_MODEL/OLLAMA_EMBED_MODEL.
  • Source de connaissance : table knowledge_document (PostgreSQL) — title, content, embedding (Json), category, metadata. Pas de fichiers, une base éditée depuis le backoffice (section 3).
  • initialize() charge tous les documents en cache RAM, génère les embeddings manquants (persistés en base), puis pré-charge le modèle texte (keep_alive: '24h').

Flux d'une question (askQuestion)

  1. Fast-path sans LLM pour salutations/remerciements/insultes/questions hors-domaine.
  2. Cache réponse (clé = question normalisée).
  3. Enrichissement contextuel avec les 2 derniers messages si la question est courte et ressemble à un suivi.
  4. Recherche hybride : mots-clés + synonymes bancaires d'abord (rapide, court-circuite l'appel embedding si le score dépasse un seuil), embeddings cosinus en repli.
  5. Réponse directe sans LLM si le meilleur document dépasse un seuil de pertinence par mots-clés.
  6. Sinon, appel au LLM Ollama avec un prompt contraint (français, 2-3 phrases, contexte Togo uniquement, historique de conversation inclus).

Donc : mots-clés d'abord, embeddings en repli, LLM en dernier recours — pas node-nlp dans ce flux réel.

Mémoire de conversation

En RAM uniquement (Map<sessionId, {...}>), pas en base — 6 messages max par session, expiration 15 min, nettoyage périodique. sessionId = l'adresse IP du client (pas un identifiant utilisateur réel) — deux utilisateurs derrière le même NAT/proxy partagent la même mémoire de conversation.

3. Gestion de la base de connaissance (backoffice)

knowledge.controller.js (CRUD REST protégé JWT) sur knowledge_document. KnowledgeManagerView.vue est un formulaire simple titre/catégorie/contenu — pas d'utterances/intents (ce concept appartient à l'ancien moteur @nlpjs, abandonné).

Prise en compte à chaud : créer/modifier/supprimer une entrée appelle immédiatement les fonctions correspondantes de rag.service.js, qui synchronisent le cache RAM et invalident le cache de réponses — aucun redémarrage serveur nécessaire, contrairement à ce qu'on pourrait attendre d'un système nécessitant un "réentraînement".

4. ChatbotWidget.vue (frontend public)

Historique de conversation gardé uniquement en mémoire du composant Vue — perdu au rafraîchissement de page, pas de persistance locale. Réponse complète (pas de streaming), timeout client de 120 secondes. Couleur du widget configurable via site-settings/CHATBOT_COLOR.

Pièges connus pour un futur développeur

  1. Vérifier server.js avant tout — le chatbot peut être silencieusement désactivé (section "Le chatbot est actuellement désactivé", ci-dessus) sans qu'aucune erreur visible n'indique pourquoi le widget ne répond pas.
  2. Ne pas confondre les deux moteurs NLPchatbot-training.json/model.nlp (ancien, inactif) vs knowledge_document/RAG (actuel, actif). Le README backend décrit encore l'ancien.
  3. sessionId = IP client — en environnement avec NAT/proxy partagé (ex. plusieurs postes d'une même agence), plusieurs utilisateurs peuvent se retrouver avec le même contexte de conversation.