#!/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()