llm_lab/examples/agent_question_reponse.py
2025-03-26 15:40:31 +01:00

337 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Agent de questions-réponses utilisant MistralAPI
"""
import os
import sys
import json
import argparse
from pathlib import Path
from typing import Dict, List, Any, Optional
from datetime import datetime
# Ajouter le répertoire parent au sys.path pour importer les modules
sys.path.append(str(Path(__file__).parent.parent))
from agents.base_agent import Agent
from core.factory import LLMFactory
class AgentQuestionReponse(Agent):
"""
Agent spécialisé dans les questions-réponses utilisant Mistral API
"""
def __init__(self, nom: str, modele: str = "mistral-large-latest"):
"""
Initialise l'agent avec un nom et un modèle Mistral
Args:
nom: Nom de l'agent
modele: Modèle Mistral à utiliser
"""
super().__init__(nom)
self.llm = LLMFactory().create("mistralapi")
self.llm.modele = modele
def executer(self, question: str, contexte: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
Exécute une requête de question-réponse
Args:
question: La question à poser
contexte: Contexte supplémentaire pour la question (optionnel)
Returns:
Dictionnaire contenant la réponse et les métadonnées
"""
# Formater le prompt avec le contexte si disponible
prompt = self._construire_prompt(question, contexte)
try:
# Générer la réponse
reponse = self.llm.generate(prompt)
# Enregistrer dans l'historique
input_data = {
"type": "question",
"contenu": question,
"contexte": contexte
}
output_data = {
"type": "reponse",
"contenu": reponse
}
self.ajouter_historique("generation_reponse", input_data, output_data)
# Préparer le résultat
resultat = {
"question": question,
"reponse": reponse,
"timestamp": datetime.now().isoformat(),
"modele": self.llm.modele
}
if contexte:
resultat["contexte_utilise"] = True
return resultat
except Exception as e:
return {
"erreur": str(e),
"question": question,
"timestamp": datetime.now().isoformat()
}
def _construire_prompt(self, question: str, contexte: Optional[Dict[str, Any]] = None) -> str:
"""
Construit le prompt en fonction de la question et du contexte
"""
if not contexte:
return question
# Formater le contexte en texte
contexte_texte = self._formater_contexte(contexte)
# Construire le prompt avec le contexte
prompt = f"""Contexte :
{contexte_texte}
Question : {question}
Réponds à la question ci-dessus en utilisant le contexte fourni.
Si le contexte ne suffit pas pour répondre complètement, indique-le clairement.
"""
return prompt
def _formater_contexte(self, contexte: Dict[str, Any]) -> str:
"""
Formate le contexte en texte lisible
"""
if isinstance(contexte, str):
return contexte
if isinstance(contexte, dict):
# Si le contexte est un dictionnaire JSON
try:
return json.dumps(contexte, ensure_ascii=False, indent=2)
except:
parts = []
for key, value in contexte.items():
parts.append(f"{key}: {value}")
return "\n".join(parts)
# Fallback pour d'autres types
return str(contexte)
def generer_questions(self, contexte: Dict[str, Any], nombre: int = 3) -> List[str]:
"""
Génère des questions pertinentes basées sur un contexte
Args:
contexte: Le contexte à partir duquel générer des questions
nombre: Nombre de questions à générer
Returns:
Liste des questions générées
"""
contexte_texte = self._formater_contexte(contexte)
prompt = f"""À partir du contexte suivant :
{contexte_texte}
Génère {nombre} questions pertinentes et intéressantes qui pourraient être posées sur ce contenu.
Format attendu : une question par ligne, numérotée (1., 2., etc.)
"""
try:
reponse = self.llm.generate(prompt)
# Extraire les questions de la réponse
questions = []
for ligne in reponse.split('\n'):
ligne = ligne.strip()
if ligne and (ligne[0].isdigit() and ligne[1:3] in ['. ', '- ', ') ']):
# Extraire la question après le numéro
question = ligne[ligne.find(' ')+1:].strip()
questions.append(question)
# Si l'extraction a échoué, retourner la réponse brute divisée
if not questions and reponse:
questions = [q.strip() for q in reponse.split('\n') if q.strip()]
return questions[:nombre] # Limiter au nombre demandé
except Exception as e:
print(f"Erreur lors de la génération de questions: {e}")
return []
def evaluer_reponse(self, question: str, reponse: str, reference: Optional[str] = None) -> Dict[str, Any]:
"""
Évalue la qualité d'une réponse à une question
Args:
question: La question posée
reponse: La réponse à évaluer
reference: Réponse de référence (optionnelle)
Returns:
Dictionnaire contenant l'évaluation et les scores
"""
if reference:
prompt = f"""Question : {question}
Réponse à évaluer :
{reponse}
Réponse de référence :
{reference}
Évalue la réponse fournie par rapport à la réponse de référence. Attribue un score pour chaque critère :
1. Précision (1-10) : La réponse est-elle factuelle et correcte ?
2. Complétude (1-10) : La réponse couvre-t-elle tous les aspects importants ?
3. Clarté (1-10) : La réponse est-elle claire et facile à comprendre ?
Pour chaque critère, justifie brièvement ton score. Termine par une note globale sur 10.
"""
else:
prompt = f"""Question : {question}
Réponse à évaluer :
{reponse}
Évalue la qualité de cette réponse. Attribue un score pour chaque critère :
1. Précision (1-10) : La réponse semble-t-elle factuelle et correcte ?
2. Complétude (1-10) : La réponse semble-t-elle couvrir tous les aspects importants ?
3. Clarté (1-10) : La réponse est-elle claire et facile à comprendre ?
Pour chaque critère, justifie brièvement ton score. Termine par une note globale sur 10.
"""
try:
evaluation = self.llm.generate(prompt)
# Tenter d'extraire les scores numériques
scores = {}
for ligne in evaluation.split('\n'):
if "précision" in ligne.lower() and ":" in ligne:
try:
score = int([s for s in ligne.split() if s.isdigit()][0])
scores["precision"] = score
except:
pass
elif "complétude" in ligne.lower() and ":" in ligne:
try:
score = int([s for s in ligne.split() if s.isdigit()][0])
scores["completude"] = score
except:
pass
elif "clarté" in ligne.lower() and ":" in ligne:
try:
score = int([s for s in ligne.split() if s.isdigit()][0])
scores["clarte"] = score
except:
pass
elif "globale" in ligne.lower() and ":" in ligne:
try:
score = int([s for s in ligne.split() if s.isdigit()][0])
scores["globale"] = score
except:
pass
return {
"evaluation_detaillee": evaluation,
"scores": scores
}
except Exception as e:
return {
"erreur": str(e),
"evaluation_detaillee": "Échec de l'évaluation"
}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Agent de Questions-Réponses avec Mistral API")
parser.add_argument("--question", help="Question à poser")
parser.add_argument("--contexte", help="Chemin vers un fichier JSON de contexte")
parser.add_argument("--model", default="mistral-large-latest", help="Modèle Mistral à utiliser")
parser.add_argument("--generer-questions", action="store_true", help="Générer des questions à partir du contexte")
parser.add_argument("--nombre", type=int, default=3, help="Nombre de questions à générer")
parser.add_argument("--evaluer", help="Réponse à évaluer (nécessite --question)")
parser.add_argument("--reference", help="Réponse de référence pour l'évaluation")
args = parser.parse_args()
# Vérification de la clé API
if not os.environ.get("MISTRAL_API_KEY"):
print("Erreur: Variable d'environnement MISTRAL_API_KEY non définie.")
print("Veuillez définir cette variable avec votre clé API Mistral.")
print("Exemple: export MISTRAL_API_KEY=votre_clé_api")
sys.exit(1)
# Initialisation de l'agent
agent = AgentQuestionReponse("AgentQR", modele=args.model)
# Chargement du contexte si spécifié
contexte = None
if args.contexte:
try:
with open(args.contexte, 'r', encoding='utf-8') as f:
contexte = json.load(f)
print(f"Contexte chargé depuis {args.contexte}")
except Exception as e:
print(f"Erreur lors du chargement du contexte: {e}")
sys.exit(1)
# Génération de questions à partir du contexte
if args.generer_questions and contexte:
print(f"\n=== Génération de {args.nombre} questions à partir du contexte ===")
questions = agent.generer_questions(contexte, nombre=args.nombre)
print("\nQuestions générées:")
for i, question in enumerate(questions, 1):
print(f"{i}. {question}")
sys.exit(0)
# Évaluation d'une réponse
if args.evaluer and args.question:
print("\n=== Évaluation de la réponse ===")
evaluation = agent.evaluer_reponse(args.question, args.evaluer, args.reference)
if "erreur" in evaluation:
print(f"Erreur: {evaluation['erreur']}")
else:
print("\nÉvaluation détaillée:")
print(evaluation["evaluation_detaillee"])
if evaluation.get("scores"):
print("\nScores:")
for critere, score in evaluation["scores"].items():
print(f"- {critere.capitalize()}: {score}/10")
sys.exit(0)
# Traitement d'une question simple
if args.question:
print(f"\n=== Traitement de la question avec le modèle {args.model} ===")
print(f"Question: {args.question}")
resultat = agent.executer(args.question, contexte)
if "erreur" in resultat:
print(f"Erreur: {resultat['erreur']}")
else:
print("\nRéponse:")
print(resultat["reponse"])
sys.exit(0)
# Si aucune action spécifiée
parser.print_help()