Spaces:
Runtime error
Runtime error
File size: 8,935 Bytes
87b004b 77c855f 87b004b 81836d8 87b004b 81836d8 87b004b | 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 | # 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.
|