Implémentation de l'historique des messages et du contexte dans un chat LLM local avec Python
Le chat en console basé sur LiteLLM et Ollama de la partie précédente traite les requêtes indépendamment. Le modèle ne peut pas voir les messages précédents, ce qui rompt le dialogue : les références aux réponses passées sont ignorées. La solution consiste à stocker l'historique des messages dans une liste Python et à le transmettre avec chaque nouvelle requête.
Cela transforme une séquence d'appels en une conversation cohérente. Le modèle reçoit le contexte complet : l'instruction système, les paires utilisateur/assistant précédentes et l'entrée actuelle.
Structure des messages pour le contexte
Le format des messages est standard pour les API compatibles OpenAI :
system: une instruction persistante, ajoutée séparémentuser: la requête de l'utilisateurassistant: la réponse du modèle
L'historique contient uniquement les paires utilisateur/assistant. Lors de l'envoi :
[
{"role": "system", "content": SYSTEM_PROMPT},
*conversation_history,
{"role": "user", "content": user_message}
]
L'ordre est critique : le système définit le comportement, l'historique fournit le contexte, le nouvel utilisateur est la requête actuelle.
Code mis à jour de main.py
Script complet avec historique et écrêtage du contexte :
# -*- coding: utf-8 -*-
import time
from typing import Optional
from litellm import completion
MODEL = "ollama_chat/qwen2.5:3b"
API_BASE = "http://localhost:11434"
SYSTEM_PROMPT = "Vous êtes un assistant utile. Répondez de manière concise et précise."
MAX_HISTORY_MESSAGES = 6
def trim_history(history: list, limit: int) -> list:
if len(history) <= limit:
return history
return history[-limit:]
def send_request_to_llm(user_message: str, history: list) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
*history,
{"role": "user", "content": user_message},
]
try:
start_time = time.time()
response = completion(
model=MODEL,
messages=messages,
api_base=API_BASE,
request_timeout=120,
)
duration = time.time() - start_time
print(f"\nTemps de génération : {duration:.2f} sec")
return response.choices[0].message.content
except Exception as e:
print(f"\nErreur de requête : {e}")
return None
def main() -> None:
print("Assistant IA local avec mémoire démarré.")
print("Entrez une question ou 'exit' pour quitter.\n")
conversation_history = []
while True:
user_input = input("Vous : ").strip()
if user_input.lower() in ("exit", "quit"):
print("Au revoir !")
break
if not user_input:
print("Veuillez entrer une question.")
continue
print("\nModèle en réflexion...")
answer = send_request_to_llm(user_input, conversation_history)
if answer is not None:
print(f"\nIA : {answer}\n")
conversation_history.append({"role": "user", "content": user_input})
conversation_history.append({"role": "assistant", "content": answer})
conversation_history = trim_history(conversation_history, MAX_HISTORY_MESSAGES)
else:
print("\nÉchec de la réponse. Vérifiez si Ollama est en cours d'exécution.\n")
if __name__ == "__main__":
main()
Changements logiques clés
- conversation_history : liste dans main() stocke les paires de messages
- send_request_to_llm : accepte l'historique, l'étend via
* - Sauvegarde des paires : après une réponse, utilisateur et assistant sont ajoutés
- trim_history : écrête à MAX_HISTORY_MESSAGES (6 éléments — 3 dialogues)
| Composant | Avant mise à jour | Après mise à jour |
|-----------|-------------------|-------------------|
| messages | système + utilisateur | système + historique + utilisateur |
| Mémoire | Aucune | Liste en RAM |
| Taille | Illimitée | Jusqu'à 6 messages |
L'écrêtage empêche le débordement de contexte : les anciens messages augmentent les tokens et ralentissent l'inférence.
Test du contexte
Dialogue avec requêtes dépendantes :
- Vous : Nommez trois frameworks de développement web Python
- IA : Django, Flask, FastAPI
- Vous : Dites-m'en plus sur le deuxième
- IA : Flask est un microframework léger...
Le modèle référence correctement "le deuxième" en voyant la réponse précédente.
Limites de l'implémentation actuelle
- Historique en RAM : perdu au redémarrage
- Limite fixe : 6 messages (configurable)
- Aucune persistance : nécessite Redis/BDD pour la production
Pour un bot Telegram ou une application web, la logique reste : historique dans la session utilisateur.
Ce qui est important
- Format des messages : système séparément, historique uniquement utilisateur/assistant
- Écrêtage de l'historique : [-limit:] préserve les paires récentes
- Séparation des responsabilités : requêtes LLM dans send_request_to_llm, état dans main()
- Timeouts : request_timeout=120 pour les longs contextes
- Journalisation : temps de génération pour le débogage des performances
— Editorial Team
Aucun commentaire pour le moment.