LogoKalli
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                    # TranslationManager

Flux 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ée

session_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)

MarqueurSignification
__MODE__:ALTERBascule vers mode orchestrateur (persisté en métadonnée thread)
__MODE__:NORMALReste 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:

  1. Le rôle de l'utilisateur est résolu via get_user_role().
  2. L'index du rôle est chargé depuis la DB (ou depuis le cache en mémoire ROLE_INDEX_CACHE).
  3. Si un index plus récent existe en DB, le cache est invalidé et rechargé.

Interface Chainlit — Fonctions clés

HookDéclencheurAction
on_chat_startNouvelle sessionInit prompt système, cache rôle admin, init logos
on_messageMessage utilisateurProcess question, stream réponse, gère marqueurs
on_chat_resumeReprise threadRestaure historique + orchestrator_mode depuis métadonnées
oauth_callbackAuth OAuthValide provider azure-ad
on_audio_start/chunk/endEntrée audioCapture, 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.