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)
```bash
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 :
```json
{
"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** :
```bash
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=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.