Chat Engine Mistral

, par Bertrand Degoy

Avec les réglages adoptés, le ChatEngine est de type C3 : CondensePlusContextChatEngine.

La classe est : <class 'llama_index.core.chat_engine.condense_plus_context.CondensePlusContextChatEngine'>

Il en résulte l’API suivante :

Méthodes par catégorie fonctionnelle


1. Méthodes publiques (API officielle)

chat(prompt)

  • Appel synchrone.
  • Retourne une réponse complète (pas de streaming).
  • Utilise la condensation + récupération de contexte + synthèse.

stream_chat(message)

  • Version streaming de chat().
  • Retourne un ChatResponse avec response_gen (générateur de tokens).
  • C’est celle que tu utilises.

achat(prompt)

  • Version async de chat().

astream_chat(message)

  • Version async de stream_chat().

chat_history

  • Liste interne des messages échangés.
  • Utilisée pour la condensation (C3).

reset()

  • Vide l’historique du chat.
  • Réinitialise l’état interne.
  • Ne ferme aucun stream (important).

from_defaults(...)

  • Constructeur de convenance.
  • Permet de créer un CondensePlusContextChatEngine avec des paramètres par défaut.

chat_repl() / streaming_chat_repl()

  • Interfaces REPL (console).
  • Pas utiles dans ton architecture Streamlit.

2. Méthodes internes liées à la condensation (C3)

C’est le cœur du moteur.

_condense_question()

  • Prend la dernière question utilisateur.
  • Condense en une question autonome.
  • Utilise _condense_prompt_template.

_acondense_question()

  • Version async.

_skip_condense

  • Booléen interne.
  • Permet de bypasser la condensation (rare).

_condense_prompt_template

  • Prompt utilisé pour la condensation.

3. Méthodes internes liées à la récupération de contexte

_get_nodes(query)

  • Appelle le retriever.
  • Retourne les nœuds pertinents.

_aget_nodes(query)

  • Version async.

_retriever

  • L’objet retriever (vector store, BM25, hybrid…).

_node_postprocessors

  • Liste de post‑processeurs appliqués aux nœuds (rerankers, filtres…).

4. Méthodes internes liées à la synthèse de réponse

_run_c3()

  • Pipeline complet :
    1. condensation
    2. récupération
    3. synthèse

_arun_c3()

  • Version async.

_get_response_synthesizer()

  • Retourne l’objet responsable de la génération finale.
  • Souvent un ResponseSynthesizer.

5. Méthodes internes liées au LLM

_llm

  • L’instance du LLM (OpenAI, Ollama, Azure, etc.).

_system_prompt

  • Prompt système injecté dans la conversation.

_token_counter

  • Compteur interne pour mesurer les tokens consommés.

_verbose

  • Active les logs détaillés.

6. Méthodes héritées / utilitaires

callback_manager

  • Gestion des callbacks (events, logs, instrumentation).

__abstractmethods__

  • Indique que la classe hérite d’une ABC.

__dict__, __repr__, etc.

  • Méthodes Python standard.

Résumé

Méthode Rôle
chat() Réponse complète
stream_chat() Streaming
achat() Async
astream_chat() Async streaming
reset() Réinitialise l’historique
_condense_question() Condensation
_get_nodes() Récupération
_run_c3() Pipeline complet
_llm LLM utilisé
chat_history Historique interne

Le pipeline C3

CondensePlusContextChatEngine applique un pipeline en 3 phases :

  1. Condense → transformer l’entrée utilisateur en une question autonome
  2. Context → récupérer les nœuds pertinents via le retriever
  3. Chat → synthétiser une réponse avec le LLM (streaming ou non)

Ce pipeline est implémenté dans _run_c3() et _arun_c3().


Étape 1 — Condensation de la question

Méthode interne : _condense_question()

Objectif :
Transformer la question utilisateur + historique en une question autonome, courte, optimisée pour le retriever.

Processus :

  1. Le moteur regarde chat_history
  2. Il prend le dernier message utilisateur
  3. Il applique _condense_prompt_template
  4. Il appelle le LLM (_llm) pour produire une version condensée
  5. Il retourne un texte propre, sans bruit conversationnel

Si _skip_condense=True, cette étape est sautée.


Étape 2 — Récupération du contexte

Méthode interne : _get_nodes()

Objectif :
Récupérer les documents pertinents pour répondre à la question condensée.

Processus :

  1. Le moteur appelle _retriever.retrieve(condensed_question)
  2. Le retriever renvoie une liste de NodeWithScore
  3. Le moteur applique les post‑processeurs (_node_postprocessors)
  4. Le moteur obtient une liste finale de nœuds pertinents

C’est ici que vector store, BM25, hybrid search, etc. interviennent.


Étape 3 — Synthèse de la réponse

Méthode interne : _get_response_synthesizer()

Objectif :
Générer la réponse finale à partir :

  • de la question condensée
  • des nœuds récupérés
  • du prompt système
  • de l’historique

Processus :

  1. Le moteur construit un prompt complet (system + context + question)
  2. Il appelle le LLM via le ResponseSynthesizer
  3. Si on utilise stream_chat() :
    • le LLM est appelé en mode streaming
    • un générateur Python est créé (response.response_gen)
    • chaque token est yieldé au fur et à mesure
  4. Le moteur met à jour chat_history avec :
    • la question condensée
    • la réponse finale

Ce qui se passe EXACTEMENT dans stream_chat()

Voici la séquence interne réelle :

1. Normalisation de l’entrée

stream_chat() accepte :

  • un dict
  • un ChatMessage
  • un QueryBundle

Il convertit tout en un ChatMessage.

2. Ajout du message utilisateur dans chat_history

Le moteur stocke le message brut.

3. Appel à _run_c3()

C’est le pipeline complet :

condensed = _condense_question()
nodes = _get_nodes(condensed)
response = synthesizer.synthesize(condensed, nodes, streaming=True)

4. Construction d’un ChatResponse

Ce n’est pas un simple objet :
c’est un wrapper contenant :

  • response_gen → le générateur de tokens
  • response → la réponse complète (remplie à la fin)
  • metadata → infos sur les nœuds, tokens, etc.

5. Retour du ChatResponse

Et c’est là que tu dois consommer le générateur.


Le point critique : le générateur

response.response_gen est un générateur Python qui encapsule :

  • un flux HTTP (OpenAI, Ollama, Azure…)
  • un pipe interne
  • un buffer de tokens

Et il n’est jamais fermé automatiquement.

Cela peut provoquer l’erreur :

OSError: [Errno 24] Too many open files

Pourquoi reset() ne ferme rien

Parce que :

  • reset() vide chat_history
  • mais ne touche pas aux générateurs
  • ni aux connexions HTTP
  • ni aux file descriptors

Donc aucun impact sur les fuites.


Comment terminer le pipeline proprement

gen = response.response_gen
try:
    for token in gen:
        ...
finally:
    gen.close()

C’est la seule manière de libérer les ressources.


Résumé

Voici le pipeline interne complet :

stream_chat()
    ↓
normalisation du message
    ↓
chat_history.append(user_message)
    ↓
_run_c3()
    ↓
_condense_question()
    ↓
_get_nodes()
    ↓
_get_response_synthesizer()
    ↓
LLM(streaming=True)
    ↓
ChatResponse(response_gen=generator)
    ↓
return ChatResponse