Spaces:
Running
Running
File size: 8,134 Bytes
56a777c 453d231 47b8a7e 1533d23 e891d20 fd2fa68 56a777c fd2fa68 56a777c fd2fa68 302d68d 56a777c 1533d23 56a777c 302d68d 1533d23 e891d20 56a777c e891d20 56a777c 1533d23 302d68d 56a777c fd2fa68 453d231 fd2fa68 56a777c fd2fa68 453d231 56a777c 453d231 56a777c 453d231 56a777c 453d231 fd2fa68 56a777c fd2fa68 229fbd9 453d231 229fbd9 453d231 229fbd9 453d231 56a777c 229fbd9 56a777c 229fbd9 56a777c 229fbd9 56a777c 453d231 56a777c fd2fa68 453d231 229fbd9 56a777c 453d231 56a777c 453d231 229fbd9 fd2fa68 56a777c fd2fa68 229fbd9 fd2fa68 229fbd9 fd2fa68 229fbd9 fd2fa68 229fbd9 456aba5 229fbd9 fd2fa68 229fbd9 fd2fa68 56a777c fd2fa68 453d231 fd2fa68 453d231 56a777c 453d231 fd2fa68 453d231 56a777c 453d231 229fbd9 453d231 56a777c 453d231 56a777c 453d231 56a777c 453d231 fd2fa68 229fbd9 56a777c fd2fa68 456aba5 56a777c fd2fa68 456aba5 56a777c 456aba5 56a777c 456aba5 fd2fa68 56a777c 456aba5 453d231 56a777c 453d231 56a777c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | # app.py — Simplified (Groq-only, no GGUF)
from __future__ import annotations
import os
import sys
import traceback
import shutil
from pathlib import Path
import gradio as gr
from huggingface_hub import hf_hub_download
# ----------------------------
# Helpers
# ----------------------------
def groq_enabled() -> bool:
return bool(os.environ.get("GROQ_API_KEY", "").strip())
def ensure_faiss_index_present():
"""
FAISS index is needed for QA retrieval.
(Groq replaces ONLY the generator, not the retrieval.)
"""
repo_id = os.environ.get("FAISS_REPO_ID", "FabIndy/code-education-faiss-index")
token = os.environ.get("HF_TOKEN") # optional if index repo is public
local_dir = Path("db/faiss_code_edu_by_article")
local_dir.mkdir(parents=True, exist_ok=True)
# Download to HF cache
f_faiss = hf_hub_download(
repo_id=repo_id,
repo_type="dataset",
filename="index.faiss",
token=token,
)
f_pkl = hf_hub_download(
repo_id=repo_id,
repo_type="dataset",
filename="index.pkl",
token=token,
)
# Copy to expected local dir
shutil.copyfile(f_faiss, local_dir / "index.faiss")
shutil.copyfile(f_pkl, local_dir / "index.pkl")
# Always ensure FAISS (required for QA retrieval)
ensure_faiss_index_present()
# ----------------------------
# Import validated RAG core
# ----------------------------
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
# rag_core imports "from src import ..." so we add project root (not /src)
if ROOT_DIR not in sys.path:
sys.path.insert(0, ROOT_DIR)
try:
from src import rag_core
except Exception as e:
raise RuntimeError(
"Impossible d'importer src/rag_core.py. "
"Vérifie que le dossier src/ contient bien rag_core.py et qu'il n'y a pas d'erreurs d'import."
) from e
# ----------------------------
# Rendering helpers
# ----------------------------
def _render_list(articles) -> str:
if not articles:
return "Aucun article trouvé."
arts = [str(a).strip() for a in articles if str(a).strip()]
arts = sorted(set(arts))
return "Articles proposés :\n" + "\n".join([f"- {a}" for a in arts])
def _format_result(result) -> str:
if result is None:
return "Aucune réponse."
if isinstance(result, str):
return result.strip() or "Aucune réponse."
if isinstance(result, dict):
mode = str(result.get("mode", "")).strip().upper()
answer = result.get("answer", result.get("response", "")) or ""
answer = str(answer).strip()
articles = result.get("articles") or []
if mode == "LIST":
return _render_list(articles)
tail = f"\n\nArticles : {', '.join(map(str, articles))}" if articles else ""
return (answer or "Aucune réponse.") + tail
return str(result).strip() or "Aucune réponse."
# ----------------------------
# Core call
# ----------------------------
def call_core(query: str) -> str:
q = (query or "").strip()
if not q:
return "Entre une demande."
# Groq-only: if missing, fail fast with a clear message
if not groq_enabled():
return (
"Groq n'est pas configuré.\n\n"
"Ajoute la variable d'environnement GROQ_API_KEY dans le Space "
"(Settings → Variables).\n"
"Optionnel : GROQ_MODEL, GROQ_MAX_TOKENS_SUMMARY, GROQ_MAX_TOKENS_QA, GROQ_TEMPERATURE."
)
try:
result = rag_core.answer_query(q)
return _format_result(result)
except Exception:
return "Erreur côté application :\n\n" + traceback.format_exc()
# ----------------------------
# Tab wrappers
# ----------------------------
def tab_list(theme: str) -> str:
t = (theme or "").strip()
if not t:
return "Entre un thème (ex : vacances scolaires, conseil de classe, obligation scolaire)."
return call_core(f"Quels articles parlent de {t} ?")
def tab_fulltext(article_id: str) -> str:
a = (article_id or "").strip()
if not a:
return "Entre un identifiant d’article (ex : D422-5, L111-1, R421-10)."
return call_core(f"Donne l’intégralité de l’article {a}")
def tab_synthese(article_id: str) -> str:
a = (article_id or "").strip()
if not a:
return "Entre un identifiant d’article (ex : D422-5)."
return call_core(f"Synthèse (points clés) de l’article {a}")
def tab_summary_ai(article_id: str) -> str:
a = (article_id or "").strip()
if not a:
return "Entre un identifiant d’article (ex : D422-5)."
return call_core(f"Résumé IA de l’article {a}")
def tab_qa(question: str) -> str:
q = (question or "").strip()
if not q:
return "Entre une question."
return call_core(q)
def clear_all():
return "", "", "", ""
# ----------------------------
# UI
# ----------------------------
CSS = """
:root { --font-sans: Inter, "Source Sans 3", Roboto, "Segoe UI", Arial, sans-serif; }
body, .gradio-container { font-family: var(--font-sans) !important; font-size: 15px; line-height: 1.5; }
.gradio-container { max-width: 980px !important; }
#answer textarea { max-height: 480px !important; overflow-y: auto !important; font-size: 14px; line-height: 1.55; }
.small-note { font-size: 13px; opacity: 0.9; }
"""
THEME = gr.themes.Soft()
with gr.Blocks(title="Code de l’éducation — Assistant (Groq)", css=CSS, theme=THEME) as demo:
gr.Markdown(
"""
# Code de l’éducation — Assistant (RAG)
- **LIST** : trouve des articles (recherche explicable)
- **Texte officiel** : affiche l’article exact
- **Résumé** :
- **Extraits officiels** : fiable (sans reformulation)
- **Résumé IA** : rapide (reformulation, peut comporter des erreurs)
- **Question (IA)** : interprétatif → toujours vérifier sur le texte officiel
> Génération **100% via Groq**.
""".strip()
)
if groq_enabled():
gr.Markdown("Groq configuré.")
else:
gr.Markdown("Groq non configuré : ajoute `GROQ_API_KEY` dans les Variables du Space.")
with gr.Tabs():
with gr.Tab("Trouver des articles"):
list_inp = gr.Textbox(label="Thème", placeholder="Ex : vacances scolaires, conseil de classe…")
list_btn = gr.Button("Chercher")
list_out = gr.Textbox(label="Résultat", elem_id="answer", lines=18)
list_btn.click(fn=tab_list, inputs=list_inp, outputs=list_out)
with gr.Tab("Texte officiel"):
ft_inp = gr.Textbox(label="Identifiant d’article", placeholder="Ex : D521-5")
ft_btn = gr.Button("Afficher")
ft_out = gr.Textbox(label="Texte officiel", elem_id="answer", lines=18)
ft_btn.click(fn=tab_fulltext, inputs=ft_inp, outputs=ft_out)
with gr.Tab("Résumé"):
s_inp = gr.Textbox(label="Identifiant d’article", placeholder="Ex : D521-5")
with gr.Row():
s_btn = gr.Button("Extraits officiels (fiable)")
ai_btn = gr.Button("Résumé IA (rapide)")
s_out = gr.Textbox(label="Résumé", elem_id="answer", lines=18)
s_btn.click(fn=tab_synthese, inputs=s_inp, outputs=s_out)
ai_btn.click(fn=tab_summary_ai, inputs=s_inp, outputs=s_out)
with gr.Tab("Question (IA)"):
qa_inp = gr.Textbox(label="Question", placeholder="Ex : Qui décide des dates de vacances scolaires ?")
qa_btn = gr.Button("Répondre")
qa_out = gr.Textbox(label="Réponse", elem_id="answer", lines=18)
qa_btn.click(fn=tab_qa, inputs=qa_inp, outputs=qa_out)
with gr.Row():
clear_btn = gr.Button("Effacer")
clear_btn.click(fn=clear_all, inputs=None, outputs=[list_inp, ft_inp, s_inp, qa_inp])
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)
|