pythonise-exercice / README_APP.md
wilenPxs
Declinaisons: originalExerciseId systematique + grille miroir + bouton Stop
77c855f
|
Raw
History Blame Contribute Delete
8.94 kB

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 :

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=None dans 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.