Spaces:
Runtime error
Runtime error
wilenPyxi commited on
Commit ·
7e0a262
1
Parent(s): f63a47d
QCM : questions qualitatives + reponse=objet complet + notations par langue (intervalles/decimales via variables FR/EN, pas de py) ; garde-fou : skip du RAG si quota OpenAI epuise (429)
Browse files- app/config.py +1 -1
- app/pipeline/analyze.py +28 -6
- app/pipeline/prompts.py +27 -0
app/config.py
CHANGED
|
@@ -23,7 +23,7 @@ TEMPLATES_DIR = PACKAGE_DIR / "web" / "templates"
|
|
| 23 |
# ── Version applicative (exposée par /health pour vérifier un déploiement) ───
|
| 24 |
# Bumper à chaque déploiement significatif : permet de répondre « à jour ? »
|
| 25 |
# sans se connecter (curl /health → champ "version").
|
| 26 |
-
APP_VERSION = "2026-07-
|
| 27 |
|
| 28 |
# ── Convention MyST (vérifiée empiriquement : 222/222 exemples plateforme) ───
|
| 29 |
# Bloc {python} = 4 backticks ; enveloppe {exercise} = 5 backticks.
|
|
|
|
| 23 |
# ── Version applicative (exposée par /health pour vérifier un déploiement) ───
|
| 24 |
# Bumper à chaque déploiement significatif : permet de répondre « à jour ? »
|
| 25 |
# sans se connecter (curl /health → champ "version").
|
| 26 |
+
APP_VERSION = "2026-07-07c — QCM (qualitatif/bilingue/objet complet) + skip quota OpenAI"
|
| 27 |
|
| 28 |
# ── Convention MyST (vérifiée empiriquement : 222/222 exemples plateforme) ───
|
| 29 |
# Bloc {python} = 4 backticks ; enveloppe {exercise} = 5 backticks.
|
app/pipeline/analyze.py
CHANGED
|
@@ -28,6 +28,17 @@ from app.rag.notions import enrich_exercise_with_notions
|
|
| 28 |
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def _rules_menu() -> str:
|
| 33 |
return "\n".join(f" - {rid} — {RULES_BY_ID[rid]['title']}" for rid in ALL_RULE_IDS)
|
|
@@ -90,7 +101,8 @@ def run_analysis_phase(content: str, model_idx: int,
|
|
| 90 |
max_tokens=6096,
|
| 91 |
)
|
| 92 |
f_notions = pool.submit(enrich_exercise_with_notions, content, xlsx_path=NOTIONS_XLSX)
|
| 93 |
-
|
|
|
|
| 94 |
retrieve_functions_context,
|
| 95 |
exercise=content,
|
| 96 |
embedding_model=RAG_EMBEDDING_MODEL,
|
|
@@ -106,11 +118,21 @@ def run_analysis_phase(content: str, model_idx: int,
|
|
| 106 |
logger.warning("Retriever de notions en échec (%s) — contexte vide.", e)
|
| 107 |
notions_ctx, lists_of_notions = "", ""
|
| 108 |
|
| 109 |
-
|
| 110 |
-
functions_ctx =
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
analysis = _parse_analysis(raw_analysis, content)
|
| 116 |
logger.info("Analyse : type=%s, %d variables, %d règles ciblées",
|
|
|
|
| 28 |
|
| 29 |
logger = logging.getLogger(__name__)
|
| 30 |
|
| 31 |
+
# Garde-fou : dès que les embeddings OpenAI renvoient un quota épuisé (429), on
|
| 32 |
+
# arrête d'appeler le RAG fonctions pour le reste de la session (sinon chaque job
|
| 33 |
+
# repaie 3 retries + ~10 s pour un catalogue de toute façon vide). Réactivé au
|
| 34 |
+
# prochain démarrage (une fois le compte OpenAI rechargé).
|
| 35 |
+
_RAG_STATE = {"off": False}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _is_quota_error(exc: Exception) -> bool:
|
| 39 |
+
s = str(exc).lower()
|
| 40 |
+
return "insufficient_quota" in s or "quota" in s or "429" in s
|
| 41 |
+
|
| 42 |
|
| 43 |
def _rules_menu() -> str:
|
| 44 |
return "\n".join(f" - {rid} — {RULES_BY_ID[rid]['title']}" for rid in ALL_RULE_IDS)
|
|
|
|
| 101 |
max_tokens=6096,
|
| 102 |
)
|
| 103 |
f_notions = pool.submit(enrich_exercise_with_notions, content, xlsx_path=NOTIONS_XLSX)
|
| 104 |
+
# RAG fonctions : sauté si les embeddings OpenAI sont déjà « à sec ».
|
| 105 |
+
f_functions = None if _RAG_STATE["off"] else pool.submit(
|
| 106 |
retrieve_functions_context,
|
| 107 |
exercise=content,
|
| 108 |
embedding_model=RAG_EMBEDDING_MODEL,
|
|
|
|
| 118 |
logger.warning("Retriever de notions en échec (%s) — contexte vide.", e)
|
| 119 |
notions_ctx, lists_of_notions = "", ""
|
| 120 |
|
| 121 |
+
if f_functions is None:
|
| 122 |
+
functions_ctx = "" # RAG désactivé (quota OpenAI épuisé plus tôt)
|
| 123 |
+
else:
|
| 124 |
+
try:
|
| 125 |
+
functions_ctx = f_functions.result()["catalogue"]
|
| 126 |
+
except Exception as e:
|
| 127 |
+
functions_ctx = ""
|
| 128 |
+
if _is_quota_error(e):
|
| 129 |
+
_RAG_STATE["off"] = True
|
| 130 |
+
logger.warning(
|
| 131 |
+
"RAG fonctions DÉSACTIVÉ pour la session : quota "
|
| 132 |
+
"d'embeddings OpenAI épuisé (429). Recharge le compte "
|
| 133 |
+
"OpenAI (platform.openai.com/billing) pour le réactiver.")
|
| 134 |
+
else:
|
| 135 |
+
logger.warning("RAG fonctions en échec (%s) — catalogue vide.", e)
|
| 136 |
|
| 137 |
analysis = _parse_analysis(raw_analysis, content)
|
| 138 |
logger.info("Analyse : type=%s, %d variables, %d règles ciblées",
|
app/pipeline/prompts.py
CHANGED
|
@@ -744,6 +744,33 @@ questionHint : indice de DÉMARRAGE qui ne révèle jamais la réponse (cf. exem
|
|
| 744 |
validés — tous en fournissent un) ; VERBATIM si la source en a un ; vide
|
| 745 |
seulement si aucun indice pertinent.
|
| 746 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
|
| 748 |
""" # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
|
| 749 |
|
|
|
|
| 744 |
validés — tous en fournissent un) ; VERBATIM si la source en a un ; vide
|
| 745 |
seulement si aucun indice pertinent.
|
| 746 |
|
| 747 |
+
QUESTION QUALITATIVE (converge/diverge, monotone ou non, bornée, vrai/faux, « la
|
| 748 |
+
limite existe-t-elle ? »…) → options TEXTUELLES bilingues {fr}`…`{en}`…`. Bonne
|
| 749 |
+
réponse = la conclusion correcte ; distracteurs = conclusions ERRONÉES crédibles
|
| 750 |
+
(« diverge vers +∞ », « croissante », « bornée par… »), en grille miroir. Pas de
|
| 751 |
+
valeur numérique dans ces options.
|
| 752 |
+
|
| 753 |
+
RÉPONSE = OBJET COMPLET : chaque option porte l'objet ENTIER visé par la question
|
| 754 |
+
— l'intervalle complet, l'ensemble S = {…} complet, le polynôme entier, la valeur
|
| 755 |
+
finale — reconstitué depuis les variables Python, JAMAIS un fragment (tout
|
| 756 |
+
l'intérêt du QCM : on présente la réponse entière, plus rien à saisir).
|
| 757 |
+
|
| 758 |
+
BILINGUE — NOTATIONS PROPRES À CHAQUE LANGUE (sinon la réponse s'affiche FAUX ;
|
| 759 |
+
vaut pour l'énoncé, CHAQUE mcqAnswer — bonne réponse ET distracteurs — et la
|
| 760 |
+
detailedSolution) :
|
| 761 |
+
• Intervalle : FR = crochets + point-virgule (borne exclue = crochet RETOURNÉ,
|
| 762 |
+
ex. `\\left] a \\,;\\, b \\right]`) ; EN = parenthèse/crochet + virgule
|
| 763 |
+
(ex. `(a, b]` ; ∞ toujours en parenthèse). → deux variables d'affichage
|
| 764 |
+
`interAffFr`/`interAffEn` (ou `pxs_Interval(...).print()` rendu par langue),
|
| 765 |
+
la bonne injectée dans CHAQUE rôle. JAMAIS la même notation dans les deux.
|
| 766 |
+
• Décimale : FR = virgule, EN = point. Un décimal affiché dans les DEUX langues
|
| 767 |
+
via UNE SEULE variable figée = BUG (il reste bloqué sur une langue). → deux
|
| 768 |
+
variables `valAffFr` (virgule) / `valAffEn` (point), la bonne dans chaque
|
| 769 |
+
rôle. (`\\py{}` est INTERDIT dans l'app : c'est la voie « une variable par
|
| 770 |
+
langue » qui règle le bilingue, pas `\\py{}`.)
|
| 771 |
+
• Entiers et coefficients LaTeX (`lc`/`pxsl_format_number`) : aucun séparateur
|
| 772 |
+
décimal → une seule variable, injectée telle quelle dans les deux langues.
|
| 773 |
+
|
| 774 |
L'ÉNONCÉ NE DONNE JAMAIS LA RÉPONSE (l'énoncé définit, la question interroge).
|
| 775 |
""" # noqa: E501 — texte normatif verbatim (valeur injectée telle quelle, accolades SIMPLES)
|
| 776 |
|