""" Avis'IA Resto : application de gestion des réponses aux avis clients Génération assistée par IA (Mistral) avec validation humaine (Human-in-the-Loop) Déploiement : Hugging Face Spaces (SDK Gradio) Prérequis : secret MISTRAL_API_KEY défini dans Settings > Variables and secrets """ import os import gradio as gr from mistralai import Mistral # --------------------------------------------------------------- # 1. Configuration et client Mistral # --------------------------------------------------------------- MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY") MODEL = "mistral-small-latest" client = Mistral(api_key=MISTRAL_API_KEY) if MISTRAL_API_KEY else None # --------------------------------------------------------------- # 2. Templates de secours (fallback) par note # Utilisés uniquement si l'API est indisponible # --------------------------------------------------------------- TEMPLATES_FALLBACK = { 1: ("Nous sommes sincèrement désolés que votre expérience n'ait pas été " "à la hauteur. Vos remarques sont précieuses et nous allons agir. " "N'hésitez pas à nous contacter directement pour en discuter."), 2: ("Merci pour votre retour honnête. Nous regrettons que votre visite " "n'ait pas été pleinement satisfaisante et nous prenons vos remarques " "très au sérieux. Nous espérons pouvoir vous reconquérir."), 3: ("Merci pour votre avis. Nous notons vos points d'amélioration et " "travaillons chaque jour à offrir une meilleure expérience. " "Au plaisir de vous accueillir à nouveau."), 4: ("Merci beaucoup pour ce retour positif ! Nous sommes ravis que votre " "expérience vous ait plu et prenons note de vos suggestions. " "À très bientôt !"), 5: ("Un immense merci pour ce superbe avis ! Toute l'équipe est touchée. " "Nous avons hâte de vous retrouver très vite !"), } # --------------------------------------------------------------- # 3. Construction du prompt à partir du contenu réel de l'avis # --------------------------------------------------------------- def construire_prompt(avis_texte: str, note: int, nom_restaurant: str) -> str: if note <= 2: consigne_ton = ( "Le client est mécontent. Adopte un ton empathique et professionnel. " "Présente des excuses sincères, reprends explicitement le ou les " "problèmes concrets mentionnés dans l'avis, et propose un contact " "direct ou un geste commercial pour rétablir la confiance." ) elif note == 3: consigne_ton = ( "Le client est mitigé. Remercie-le, reconnais les points négatifs " "qu'il mentionne, valorise les points positifs, et montre que ses " "remarques seront prises en compte." ) else: consigne_ton = ( "Le client est satisfait. Adopte un ton chaleureux et reconnaissant. " "Reprends un ou deux éléments précis qu'il a appréciés et invite-le " "à revenir." ) return f"""Tu es le gérant du restaurant {nom_restaurant}. Rédige une réponse publique à l'avis client ci-dessous. Avis du client ({note}/5 étoiles) : \"\"\"{avis_texte}\"\"\" Consignes : - {consigne_ton} - Réponds en français, en 3 à 5 phrases maximum. - Personnalise la réponse avec les éléments concrets de l'avis (plats, service, ambiance...). Ne reste jamais générique. - Ne mentionne jamais que tu es une IA. - Pas de formule pompeuse, un ton naturel de restaurateur qui répond à ses clients. - Termine par une signature simple : "L'équipe de {nom_restaurant}". Réponds uniquement avec le texte de la réponse, sans commentaire ni guillemets.""" # --------------------------------------------------------------- # 4. Génération de la réponse avec bascule automatique sur fallback # --------------------------------------------------------------- def generer_reponse(avis_texte: str, note: int, nom_restaurant: str): """Retourne (reponse, source) où source vaut 'llm' ou 'fallback'.""" note = int(note) avis_texte = (avis_texte or "").strip() nom_restaurant = (nom_restaurant or "notre restaurant").strip() if not avis_texte: return TEMPLATES_FALLBACK.get(note, TEMPLATES_FALLBACK[3]), "fallback" if client is None: return TEMPLATES_FALLBACK.get(note, TEMPLATES_FALLBACK[3]), "fallback" try: reponse_api = client.chat.complete( model=MODEL, messages=[ { "role": "user", "content": construire_prompt(avis_texte, note, nom_restaurant), } ], temperature=0.7, max_tokens=300, ) texte = reponse_api.choices[0].message.content.strip() if texte: return texte, "llm" return TEMPLATES_FALLBACK.get(note, TEMPLATES_FALLBACK[3]), "fallback" except Exception as e: print(f"[Avis'IA Resto] Erreur API Mistral, bascule sur template : {e}") return TEMPLATES_FALLBACK.get(note, TEMPLATES_FALLBACK[3]), "fallback" # --------------------------------------------------------------- # 5. Fonction branchée sur l'interface Gradio # --------------------------------------------------------------- def repondre_avis(avis_texte, note, nom_restaurant): reponse, source = generer_reponse(avis_texte, note, nom_restaurant) if source == "fallback": info = "ℹ️ Réponse générée en mode standard (modèle IA indisponible)." else: info = "✅ Réponse générée par IA (Mistral). Relisez et ajustez avant publication." return reponse, info # --------------------------------------------------------------- # 6. Interface Gradio # --------------------------------------------------------------- with gr.Blocks(title="Avis'IA Resto") as demo: gr.Markdown( """ # 🍽️ Avis'IA Resto ### Réponses aux avis clients assistées par IA, validées par l'humain Générez une proposition de réponse personnalisée, puis relisez et ajustez avant publication. L'humain garde toujours la main. """ ) with gr.Row(): with gr.Column(): nom = gr.Textbox( label="Nom du restaurant", value="Chez Coralie", ) avis = gr.Textbox( label="Texte de l'avis client", lines=6, placeholder="Collez ici l'avis du client...", ) etoiles = gr.Slider( minimum=1, maximum=5, step=1, value=3, label="Note (étoiles)", ) bouton = gr.Button("Générer la réponse", variant="primary") with gr.Column(): sortie = gr.Textbox( label="Réponse proposée (modifiable avant publication)", lines=8, interactive=True, ) statut = gr.Markdown() regenerer = gr.Button("🔄 Régénérer une autre version") bouton.click( fn=repondre_avis, inputs=[avis, etoiles, nom], outputs=[sortie, statut], ) regenerer.click( fn=repondre_avis, inputs=[avis, etoiles, nom], outputs=[sortie, statut], ) if __name__ == "__main__": demo.launch()