Chatbot
Chatbot Overview
Vue d'ensemble du Chatbot
Le chatbot est le moteur IA partagé de la plateforme nAI'vi. Il combine RAG, fine-tuning optionnel et un mode orchestrateur pour fournir des réponses contextuelles depuis une base de connaissances administrée.
Rôle dans la plateforme
- Tourne en tant que service Chainlit (interface conversationnelle web-native).
- Se connecte à PostgreSQL pour récupérer les chunks, exemples Q&A et index FAISS sérialisés.
- Utilise l'API Mistral pour les embeddings et la génération de réponses (streaming).
- Expose une API FastAPI secondaire (
index_api) pour la régénération des index à la demande. - Partage une base de code commune (
common/,config/) réutilisable par d'autres bots.
Structure des modules
apps/chatbot/src/
├── chatbot/
│ ├── core/
│ │ ├── orchestration.py # process_question(), routing RAG/orchestrateur
│ │ ├── intent_classifier.py # classify_intent_and_answer(): NORMAL vs ALTER
│ │ └── external_agent.py # forward_to_external_agent(): streaming SSE vers agent externe
│ ├── rag/
│ │ ├── services.py # prepare_rag_context(), answer_question(), run_query_mistral()
│ │ └── indexing.py # get_text_embedding(), ensure_index_loaded_or_created(), regenerate_index()
│ ├── interfaces/
│ │ ├── chainlit/app.py # Hooks Chainlit: on_chat_start, on_message, audio, resume
│ │ └── api/index_api.py # FastAPI: GET /, POST /regenerate-index
│ ├── finetuning/ # Pipeline fine-tuning Mistral (JSONL → job API)
│ └── copilot/eYoma_mock/ # Prototype d'intégration copilot Teams
├── common/
│ ├── db.py # Connexion DB, queries chunks/QA/rôles/embeddings
│ ├── mistral.py # initialize_mistral_client()
│ ├── logo_loader.py # Chargement logos DB → /app/public/
│ └── logo_startup.py # Cycle de vie système de logos
└── config/
├── config.py # get_mistral_api_key(), get_db_config()
├── rag_config.py # Paramètres RAG (k, n, modèles, temp, batch_size)
├── orchestrator_config.py # is_orchestrator_enabled(), get_external_agent_url()
└── i18n.py # TranslationManagerFlux de traitement principal — Mode RAG standard
Utilisateur → question
→ process_question(question, k, n, session_mode, conversation_history, user_id)
→ validate_rag_parameters(k, n)
→ prepare_rag_context(client, db_conn, question, k, n, user_id)
→ get_user_role(db_conn, user_id) # rôle → role_id pour index per-role
→ init_rag(client, db_conn, role_id) # charge FAISS depuis DB (cache per-role)
→ get_text_embedding(client, [question]) # embedding via mistral-embed
→ index.search(q_embedding, k) # top-k chunks FAISS
→ get_few_shots_examples(db_conn, n) # n exemples Q&A (few-shot)
→ answer_question(...)
→ create_prompt(question, chunks, examples) # formate BOT_SYSTEM_PROMPT
→ run_query_mistral(client, prompt, model, history) # stream LLM
→ yield tokens + __SOURCES__:{json}Flux de traitement — Mode Orchestrateur
Activé par ENABLE_ORCHESTRATOR=true. Ajoute une étape de classification d'intention avant le RAG.
process_question(session_mode="NORMAL", ...)
→ si session_mode == "ALTER": forward_to_external_agent() directement (one-way)
→ sinon:
→ prepare_rag_context(...)
→ classify_intent_and_answer(client, question, context, few_shots, history)
→ appel LLM (mistral-medium-latest, temp=0.0)
→ réponse commence par "ALTER" → yield __MODE__:ALTER
→ forward_to_external_agent(question, user_id, history) # via SSE
→ sinon → yield __MODE__:NORMAL + réponse streaméesession_mode est unidirectionnel (NORMAL → ALTER possible, jamais retour). Il est persisté dans les métadonnées du thread Chainlit pour la reprise de conversation.
Marqueurs de protocole (streaming)
| Marqueur | Signification |
|---|---|
__MODE__:ALTER | Bascule vers mode orchestrateur (persisté en métadonnée thread) |
__MODE__:NORMAL | Reste en mode RAG |
__STEP__:tool:{message} | Affiche un step intermédiaire (Chain-of-Thought) |
__SOURCES__:{json} | JSON array: [{chunk_id, file_id, file_name, table_source}] |
Sources et traçabilité
Les réponses RAG incluent les sources. Selon le rôle de l'utilisateur:
- Admin (
is_super_admin: true): liens cliquables vers la visualisation Web —{WEB_APP_BASE_URL}/chunk-visualisation/{fileId}?chunk={chunkId}ou/admin/qa-visualisation/... - Utilisateur standard: uniquement le nom du fichier source.
Index FAISS per-role
Chaque rôle applicatif dispose de son propre index FAISS (stocké sérialisé dans la table indexes). À la résolution d'une question:
- Le rôle de l'utilisateur est résolu via
get_user_role(). - L'index du rôle est chargé depuis la DB (ou depuis le cache en mémoire
ROLE_INDEX_CACHE). - Si un index plus récent existe en DB, le cache est invalidé et rechargé.
Interface Chainlit — Fonctions clés
| Hook | Déclencheur | Action |
|---|---|---|
on_chat_start | Nouvelle session | Init prompt système, cache rôle admin, init logos |
on_message | Message utilisateur | Process question, stream réponse, gère marqueurs |
on_chat_resume | Reprise thread | Restaure historique + orchestrator_mode depuis métadonnées |
oauth_callback | Auth OAuth | Valide provider azure-ad |
on_audio_start/chunk/end | Entrée audio | Capture, détection silence, transcription Voxtral |
Choix architecturaux
- Python + Chainlit: écosystème IA mature, streaming natif, UI conversationnelle sans dev frontend.
- FAISS per-role + persistance DB: chaque rôle voit uniquement les chunks pertinents; pas de recalcul des embeddings à chaque redémarrage.
- Mistral API: embeddings (
mistral-embed) et LLM (mistral-medium-latest) dans le même fournisseur. - Lazy-init: client Mistral et connexion DB initialisés à la première question.
- Mode orchestrateur optionnel: architecture extensible sans changer le cœur RAG.
Kalli