Spaces:
Sleeping
Pythonise Exercice — v2
App Flask à deux modes :
- Pythonisation d'exercices PyxiScience MyST (valeurs fixes → randomisées) ;
- Déclinaisons (2026-07) : génération de versions QCM (MCQ) et/ou
QAT (FGQ, champs libres ordonnés) d'un exercice, randomisées elles aussi.
:originalExerciseId:systématique (=:id:du QST source, vide sinon) ; distracteurs MCQ en grille miroir (variations symétriques — la bonne réponse ne se devine jamais par la forme ; règle enseignante 2026-07-02).
Un bouton ⏹ Arrêter (tous modes) annule le job en cours : arrêt coopératif
au prochain point d'étape (POST /api/jobs/<id>/cancel), résultats partiels
conservés et téléchargeables.
Pipeline async : analyse LLM + notions + RAG (parallèles, partagés entre QCM
et QAT) → génération par paires → audit LLM → filets déterministes → porte
harnais (100 graines + contrôles étendus déclinaisons : unicité des options
MCQ sur toutes les graines, 1 seule bonne réponse, « None » en dernier, arité
FGQ, :solution: JSON) ; verdict exposé dans l'UI.
Architecture (refonte 2026-06)
run.py Lancement : python run.py (ou python -m app)
app/
├── __init__.py create_app() + logging + .env
├── config.py TOUTE la config : modèles, PYTHON_FENCE_BACKTICKS=4,
│ ANALYSIS_MODEL_IDX, RAG_TOP_K, USE_REASONING,
│ DEFAULT_LANG, JOB_TTL, chemins, prix
├── server.py routes Flask (/, /health, /api/models, /api/jobs)
├── keys.py chargement des clés API (.env)
├── pipeline/
│ ├── orchestrator.py chef d'orchestre (1 exercice)
│ ├── analyze.py analyse + notions + RAG en PARALLÈLE
│ ├── generate.py découpage + génération par paires
│ ├── audit.py audit LLM ≤2 itérations + filet de sécurité patches
│ ├── postprocess.py filets déterministes (auto-lift {{}}, ${}, fences 4,
│ │ renommage camelCase, décimales/langue, dédoublonnage)
│ ├── solutions.py substitution des solutions validées (règle 8.1)
│ ├── translate.py langue cible fr/en/both (prose seule, Python masqué)
│ ├── prompts.py tous les prompts (v1 archivée dans knowledge/)
│ └── fewshots.py sélection d'un exemple canonique par type détecté
├── rag/ functions.py (FAISS) · formatter.py · notions.py ·
│ catalogue.py (catalogue curé injecté par domaine)
├── llm/ client.py (OpenRouter + reasoning opt.) · cost.py
├── validation/ harness.py (porte qualité) · sandbox.py (exec sandboxée)
├── knowledge/ pythonisation_rules.md · rules_digest.py ·
│ fewshots/ (exemples VERTS) · prompts_v1_archive.md ·
│ functions_catalogue.md (catalogue curé des helpers)
├── corpus/ 5 fichiers de fonctions PyxiScience (RAG)
└── web/templates/index.html UI (batch, langue, verdict harnais, coût)
data/ notions.xlsx · faiss_cache/
tests/smoke.py smoke tests hors-ligne (LLM mocké)
Installation (WSL Ubuntu)
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # faiss-cpu + sentence-transformers ≈ 1.5 Go
cp .env.example .env # renseigner OPENROUTER_API_KEY / OPENAI_API_KEY
python run.py # http://127.0.0.1:5000
Smoke tests (sans réseau) : .venv/bin/python tests/smoke.py
Validation d'une sortie : python ../.claude/skills/validation-harness/harness.py <fichier>.md --seeds 300
Endpoints
| Méthode | URL | Description |
|---|---|---|
| GET | / |
UI web |
| GET | /health |
sanity check |
| GET | /api/models |
roster des modèles + défauts |
| POST | /api/jobs |
démarre un job (1 fichier OU batch) → {job_id} |
| GET | /api/jobs/<id> |
suivi + résultats par fichier (harnais, coût, langue) |
| GET | /api/jobs/<id>/download |
ZIP de toutes les sorties .md + _recapitulatif.md |
POST body :
{
"files": [{"filename": "exo.md", "content": "<MyST>"}, ...],
"lang": "fr | en | both",
"level": "",
"model_idx": 1,
"mode": "pythonise | declinaisons",
"types": {"qcm": true, "qat": true}
}
(mode absent ⇒ pythonise, rétro-compatible ; types requis en mode
declinaisons, au moins un true — les deux cochés ⇒ 2 fichiers par source,
nommés <source>_QCM.md / <source>_QAT.md, avec UNE seule analyse partagée.
Rétro-compat : {"content": "...", "filename": "..."} accepté pour 1 fichier.)
Résultat par fichier : exercise, analysis, notions, audit_patches,
warnings, harness {ok, seeds, summary}, lang {source, target, action},
cost {usd, eur, requests}, duration_s.
Téléchargement (UI, panneau Résultat) : bouton « Télécharger .md »
(sortie pythonisée du fichier affiché, suffixe _pythonise.md pour ne pas
écraser la source) ; en batch, bouton « Tout (.zip) » → ZIP de toutes
les sorties + un _recapitulatif.md (verdict harnais / warnings / coût par fichier).
Politique de modèles & banc d'essai (2026-07)
4 politiques (policy dans POST /api/jobs, sélecteur dans l'UI) :
auto (défaut — pré-classifieur de difficulté, départ sur l'échelle par coût
croissant, escalade d'un échelon à chaque échec harnais, plafonnée best) ·
best (qualité max) · cheap (le moins cher qui tient SEUIL_VERT=0.90) ·
manual (IDs explicites par rôle : models {generate, audit, mecanique}).
Rôles : generate (+réparation), audit, mecanique (analyse, traduction,
substitutions). Télémétrie de l'échelon gagnant dans result.policy_telemetry.
claude-fable-5 est retiré de toutes les listes (choix volontaire).
Clés API : tout fonctionne avec la seule OPENROUTER_API_KEY (route de
repli universelle). Clés directes optionnelles par fournisseur :
ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY,
DEEPSEEK_API_KEY, MOONSHOT_API_KEY, ZAI_API_KEY, MINIMAX_API_KEY,
MISTRAL_API_KEY — un modèle sans aucune clé est « non testé », jamais bloquant.
Banc d'essai :
python -m bench run # rôles/modèles dispo, corpus échantillonné
python -m bench run --roles generate --models claude-sonnet-5,deepseek-v4-pro
python -m bench run --exos all --types both --seuil 0.92
python -m bench run --dry-run # plomberie mockée (CI, aucune clé)
python -m bench consolidate # fusionne les runs partiels/parallèles
Sorties : bench/results/<ts>.json|.csv|_rapport.md + mise à jour de
app/models/recommended.json (protégé par manual_override: true).
Un run filtré --models n'écrit jamais recommended.json ; consolidate
fusionne tous les résultats (la cellule la plus récente gagne, restreinte
à l'échantillon courant du rôle) puis réécrit la recommandation globale.
Plusieurs bancs peuvent tourner en parallèle (un processus = un compteur
de coûts isolé) ; ne pas lancer d'autre job LLM dans le même processus.
Corpus : bench/corpus/*.md (ajouter un exercice = y déposer un .md).
Prix : app/models/prices.json — à re-vérifier avant prod, ça change
chaque semaine.
Sécurité
🚨 Les clés API dans .env étaient en clair dans le zip d'origine —
considère-les comme exposées et rotate-les :
- OpenRouter : https://openrouter.ai/keys
- OpenAI : https://platform.openai.com/api-keys
La sandbox (app/validation/sandbox.py) exécute le code généré par le LLM
avec builtins restreints, imports whitelistés et timeout (mono-poste : le
modèle de menace est l'accident LLM, pas un adversaire).
Notes
- Premier lancement : reconstruit le cache FAISS si absent (≈ 30 s).
- Modèles (IDs vérifiés sur OpenRouter 2026-07-02) : Opus 4.8, Sonnet 5
(défaut), Haiku 4.5, Gemini 2.5 Pro, GPT-5.4 — catalogue complet (16
modèles) dans
app/models/catalog.py. Fable 5 retiré volontairement. - L'étape d'analyse suit le modèle choisi par l'utilisateur
(
ANALYSIS_MODEL_IDX=Nonedans config.py pour ce comportement). - Mode batch : fichiers traités séquentiellement ; un échec n'arrête pas les autres ; récapitulatif VERT/ROUGE/erreurs + coût total.