mirror of
https://github.com/Ladebeze66/llm_lab.git
synced 2025-12-13 10:46:50 +01:00
337 lines
12 KiB
Python
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() |