El Mehdi BELAHNECH
add: index FAISS + PKL (force push via Git LFS)
0ea7e64
# app.py
# ======
# Ce script fait 3 choses :
# 1. Charge un index FAISS qui contient nos documents (Wikipédia ici)
# 2. Branche un LLM gratuit (Llama-3-8B-Instruct via Groq) grâce à LangChain
# 3. Crée une interface Gradio où l'utilisateur tape une question et reçoit
# une réponse générée + les sources utilisées
# ------------------------------------------------------------------
# 0. Imports standards
# ------------------------------------------------------------------
import os
from dotenv import load_dotenv # charge les variables définies dans .env (clé API)
##
import os, subprocess
if not os.path.exists("faiss_open_index/index.pkl"):
subprocess.run(["python", "index_open_faiss.py"], check=True)
# ------------------------------------------------------------------
# 1. Imports LangChain : LLM + chaîne de réponse
# ------------------------------------------------------------------
from langchain_groq import ChatGroq # wrapper Groq (LLM gratuit, rapide)
from langchain.chains import RetrievalQA # chaîne "question → réponse" avec contexte
from langchain.prompts import PromptTemplate # template pour dire au LLM comment répondre
# ------------------------------------------------------------------
# 2. Imports pour le vecteur-store (base de connaissances)
# ------------------------------------------------------------------
from langchain_community.vectorstores import FAISS # index de similarité
from langchain_community.embeddings import HuggingFaceEmbeddings # modèle d'embeddings
# ------------------------------------------------------------------
# 3. Imports UI
# ------------------------------------------------------------------
import gradio as gr
# ------------------------------------------------------------------
# 4. Chargement de la clé API Groq (dans .env, non commitée)
# ------------------------------------------------------------------
load_dotenv() # lit le fichier .env local
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
if not GROQ_API_KEY:
raise ValueError("Clé GROQ_API_KEY manquante dans .env ou variables HF Spaces")
# ------------------------------------------------------------------
# 5. Initialisation du LLM
# ------------------------------------------------------------------
# ChatGroq : interface OpenAI-compatible → pas de carte bleue, 30 k tokens/h gratuits
llm = ChatGroq(
model="llama-3.1-8b-instant", # modèle open-source hébergé par Groq
temperature=0.3, # 0 = très déterministe, 1 = très créatif
groq_api_key=GROQ_API_KEY,
)
# ------------------------------------------------------------------
# 6. Template de prompt : on guide le LLM pour qu’il réponde en français
# et qu’il reste concis en utilisant le contexte fourni
# ------------------------------------------------------------------
prompt_template = """Tu es un assistant scientifique francophone.
Réponds de manière concise et claire en utilisant uniquement le contexte ci-dessous.
Si le contexte ne permet pas de répondre, dis simplement « Je ne sais pas. »
Contexte :
{context}
Question : {question}
Réponse (3-4 phrases max) :"""
PROMPT = PromptTemplate(
input_variables=["context", "question"],
template=prompt_template,
)
# ------------------------------------------------------------------
# 7. Chargement de l’index FAISS (base de connaissances)
# ------------------------------------------------------------------
INDEX_DIR = "faiss_open_index" # dossier créé précédemment
EMBED_MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
embeddings = HuggingFaceEmbeddings(
model_name=EMBED_MODEL,
encode_kwargs={"normalize_embeddings": True},
)
# allow_dangerous_deserialization=True car l’index a été sauvé localement
vectorstore = FAISS.load_local(INDEX_DIR, embeddings, allow_dangerous_deserialization=True)
# ------------------------------------------------------------------
# 8. Chaîne de réponse : RetrievalQA
# - retriever : trouve les 3 passages les plus proches
# - llm : génère la réponse à partir de ces passages
# ------------------------------------------------------------------
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # on « bourre » tous les passages dans le prompt
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": PROMPT},
)
# ------------------------------------------------------------------
# 9. Fonction appelée par Gradio
# ------------------------------------------------------------------
def answer_question(question: str) -> str:
"""Pose la question au LLM et renvoie la réponse + liens sources."""
question = question.strip()
if not question:
return "<i>Entre une question…</i>"
# 1) réponse générée
answer = qa_chain.run(question)
# 2) on récupère les documents utilisés pour afficher les sources
docs = vectorstore.similarity_search(question, k=3)
# 3) mise en forme HTML rapide
sources = []
for i, d in enumerate(docs, 1):
title = d.metadata.get("title", "—")
url = d.metadata.get("url", "#")
snippet = (d.page_content[:300] + "…").replace("\n", " ")
sources.append(
f"<div style='margin:8px 0;padding:8px;border:1px solid #ddd;border-radius:8px'>"
f"<b>{i}. {title}</b><br/>"
f"<a href='{url}' target='_blank'>{url}</a><br/>"
f"<span style='opacity:.8'>{snippet}</span>"
f"</div>"
)
return f"<b>Réponse :</b><br/>{answer}<br/><br/><b>Sources :</b><br/>" + "\n".join(sources)
# ------------------------------------------------------------------
# 10. Interface Gradio
# ------------------------------------------------------------------
with gr.Blocks(theme=gr.themes.Soft(), title="Experiment Brief Q&A") as demo:
gr.Markdown("## 🔎 Experiment Brief — Q&R avec Llama-3 (Groq) + FAISS")
with gr.Row():
q = gr.Textbox(label="Ta question", placeholder="Ex. Quelle est la différence entre interleaving et A/B testing ?")
go = gr.Button("Répondre")
out = gr.HTML()
go.click(answer_question, inputs=q, outputs=out)
# ------------------------------------------------------------------
# 11. Lancement
# ------------------------------------------------------------------
if __name__ == "__main__":
demo.launch()