diff --git a/MyIA.AI.Notebooks/GenAI/Texte/03c_Constrained_Decoding_Python.ipynb b/MyIA.AI.Notebooks/GenAI/Texte/03c_Constrained_Decoding_Python.ipynb index e2b438a152..d88f771d4c 100644 --- a/MyIA.AI.Notebooks/GenAI/Texte/03c_Constrained_Decoding_Python.ipynb +++ b/MyIA.AI.Notebooks/GenAI/Texte/03c_Constrained_Decoding_Python.ipynb @@ -54,7 +54,9 @@ "\n", "- `transformers` pour le tokenizer\n", "- Une expression régulière simple pour la cible\n", - "- (optionnel, section 3) une bibliothèque de référence pour la grammaire" + "- (optionnel, section 3) une bibliothèque de référence pour la grammaire\n", + "\n", + "**Position dans la série.** Après le typage des décisions au niveau du message (03b), ce carnet descend d'un cran : le contrat s'applique **au token élu**, avant même qu'il existe. C'est le seul étage où une garantie est **par construction** plutôt que vérifiée a posteriori — tout ce qui se passe en dessous (greedy, échantillonnage, température) ne peut plus casser le format." ] }, { @@ -113,6 +115,16 @@ "print(f\"Taille vocabulaire : {tok.vocab_size:,} tokens\")" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-01", + "metadata": {}, + "source": [ + "### Lecture — le vocabulaire est la matière première du contrat\n", + "\n", + "Le tokenizer seul est chargé ici : le modèle complet (~1 Go en RAM CPU) ne sert pas encore. Le chiffre qui compte pour toute la suite est la **taille du vocabulaire : 151 643 tokens** pour Qwen2.5-0.5B-Instruct. C'est sur cet espace que le masque de logits devra trancher à chaque pas — toute contrainte se mesurera donc en « combien de tokens survivent sur 151 643 ». L'avertissement HF (requêtes non authentifiées) est sans incidence sur la suite : le tokenizer est déjà en cache local." + ] + }, { "cell_type": "markdown", "id": "1341bfd7", @@ -134,7 +146,9 @@ "produire une date invalide, un JSON cassé, ou une réponse qui viole un schéma.\n", "\n", "On simule ici le mécanisme : on charge des logits aléatoires et on regarde quels\n", - "tokens sont dans le top-5." + "tokens sont dans le top-5.\n", + "\n", + "**Le coût du sans-contrainte.** Une sortie hors format ne se contente pas d'être laide : elle casse le parseur aval, force des retries, et surtout **fabrique du contenu plausible mais faux** (un champ rempli, mais mal). La stratégie « générer puis réparer » paie chaque erreur deux fois — une fois en génération, une fois en correction. La stratégie préventive, elle, coupe l'erreur à la source : si le token interdit n'est jamais élu, il n'y a rien à réparer." ] }, { @@ -187,6 +201,16 @@ " print(f\" id={tid:>7} token={repr(tok.decode([tid])):>20} logit={fake_logits[tid]:.3f}\")" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-02", + "metadata": {}, + "source": [ + "### Lecture — sans contrainte, l'argmax tombe n'importe où\n", + "\n", + "Les logits sont simulés par une gaussienne seedée (seed 42), et pourtant le top-5 suffit à exposer le problème : `' Ensure'`, `' העלי'`, `'ควบคุม'`, `' giành'`, puis `'[]\\r\\n\\r\\n'` — quatre langues différentes et une séquence de ponctuation, sur des logits tous dans [4.08, 4.77]. Aucune structure n'émerge d'une distribution libre : l'argmax élit un token plausible **en soi**, jamais plausible **pour le format visé**. La contrainte doit donc agir sur la distribution **avant** l'argmax, pas réparer la sortie après coup." + ] + }, { "cell_type": "markdown", "id": "f2b8a04d", @@ -219,7 +243,9 @@ "\n", "La difficulté technique : un token du vocabulaire peut couvrir **plusieurs caractères**\n", "(`2026` peut être un seul token). Il faut donc raisonner sur l'**enchaînement** des\n", - "caractères dans chaque token." + "caractères dans chaque token.\n", + "\n", + "**Pourquoi un DFA et pas une regex.** Une regex matche une chaîne **complète**, a posteriori ; un DFA expose un **état incrémental** — je sais où j'en suis après chaque caractère, donc quels caractères restent acceptables. C'est exactement l'interface dont la génération a besoin : à mi-chemin d'une date, la question n'est pas « cette date est-elle valide » mais « **ce prochain caractère** est-il viable dans l'état courant ». Le déterminisme garantit en outre une transition unique par (état, caractère), donc un rejeu fidèle sur n'importe quel candidat." ] }, { @@ -240,7 +266,9 @@ "\n", "L'état encode :\n", "- combien de chiffres on a déjà vus dans le segment courant (0..4 ou 0..2) ;\n", - "- combien de séparateurs on a vus (0, 1, ou 2)." + "- combien de séparateurs on a vus (0, 1, ou 2).\n", + "\n", + "Cet encodage bidimensionnel (avancement dans le segment × séparateurs consommés) suffit parce que le langage cible est **régulier** : aucune mémoire non bornée n'est nécessaire. On le vérifiera par effet de bord : l'exercice 3 ajoutera la plage numérique **dans l'état même** (mois 01-12, jours 01-31), ce qui grossit l'automate mais ne change pas sa nature." ] }, { @@ -374,6 +402,16 @@ " print(f\" {date!r:>16} -> {'accepté' if ok else 'refusé'}\")" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-03", + "metadata": {}, + "source": [ + "### Lecture — le DFA garantit la syntaxe, pas la sémantique\n", + "\n", + "Sur les cinq candidats, `'2026-09-28'` et `'2026-13-45'` sont **acceptés** ; `'202-09-28'` (3 chiffres), `'abcd-ef-gh'` (non numérique) et `'2026-09-28T'` (caractère en trop après date complète) sont refusés. Le cas `'2026-13-45'` est le pivot pédagogique : la structure `\\d{4}-\\d{2}-\\d{2}` est respectée, mais le mois 13 et le jour 45 n'existent pas. Un automate de format ne connaît que la **forme** ; la **plage numérique** est une contrainte orthogonale, que l'exercice 3 traitera par une variante dédiée de l'automate." + ] + }, { "cell_type": "markdown", "id": "93c87bd9", @@ -399,7 +437,9 @@ "`'---------'` ne contient que des tirets, mais l'automate ne peut en consommer qu'un\n", "avant de changer d'état — la simulation le rejette donc, là où un filtre de\n", "caractères seul l'admettrait.\n", - "" + "\n", + "\n", + "**Le coût du masquage.** Construire le masque demande de rejouer **chaque token du vocabulaire** sur un clone de l'automate — jusqu'à 151 643 tests par pas dans le pire cas. C'est le prix de la différence token/caractère : un token peut porter **plusieurs caractères** (`'2026'`, `'-'`, `'---------'`), et il n'est autorisé que si l'automate peut consommer sa suite **entière**. Cette subtilité, invisible à l'échelle caractère, est ce qui distingue le constrained decoding du simple filtrage de caractères." ] }, { @@ -478,6 +518,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-04", + "metadata": {}, + "source": [ + "### Lecture — le masque réduit 151 643 tokens à 10, puis à 1\n", + "\n", + "À l'état initial `(0, 0)` — zéro chiffre vu, zéro séparateur — exactement **10 tokens** sont autorisés : les chiffres `'0'`…`'9'`. À l'état `(4, 0)` — quatre chiffres consommés — il n'en reste **qu'un seul : `'-'`**. Un token multi-caractères n'est autorisé que si l'automate peut consommer **tous** ses caractères depuis l'état courant : c'est ce rejet qui empêche un token comme `'---------'` de court-circuiter le format. La « directivité » d'une contrainte se lit déjà dans ces cardinaux." + ] + }, { "cell_type": "markdown", "id": "3ab76077", @@ -495,7 +545,7 @@ "### Démonstration end-to-end : on génère une date pas à pas\n", "\n", "On simule des logits (un modèle réel fournirait `model(input_ids).logits`) et on\n", - "applique le masque à chaque pas. On observe que la sortie reste **syntaxiquement\n", + "applique le masque à chaque pas. La sortie reste **syntaxiquement\n", "valide** par construction." ] }, @@ -588,6 +638,16 @@ " print(f\" étape {i:>2} \" + (repr(entry) if kind_label else f\"emit={entry[0]!r:>6} état={entry[1]} tokens_disponibles={entry[2]:,}\"))" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-05", + "metadata": {}, + "source": [ + "### Lecture — dix étapes, dix élections dans un ensemble autorisé\n", + "\n", + "La date générée `'2927-70-45'` est conforme au format (regex `\\d{4}-\\d{2}-\\d{2}` : True), et la trace pas-à-pas détaille le mécanisme : les étapes 0-3 élisent des chiffres (10 choix chacune), l'étape 4 n'a **qu'un token disponible** (`'-'`), et de même l'étape 7. La contrainte n'**invente** jamais de caractère : elle restreint l'élection à l'ensemble autorisé, et l'argmax (ici simulé, seed 42) choisit dedans. Le format est garanti **par construction**, pas vérifié après génération." + ] + }, { "cell_type": "markdown", "id": "eaa058b2", @@ -622,7 +682,9 @@ " `lm-format-enforcer`) — verdict SOTA à écrire dans la PR ;\n", "- l'**effet sur la latence** : le masque de logits a un coût mesurable.\n", "\n", - "Ces points sont livrés dans les tranches suivantes (cf. issue #18212)." + "Ces points sont livrés dans les tranches suivantes (cf. issue #18212).\n", + "\n", + "**Extension prospective.** La tranche 1 couvre la forme ; la plage numérique (exercice 3) couvre le fond — mais par une **variante d'automate**, pas par le mécanisme. La ligne de fond à retenir : toute contrainte qu'un automate fini peut encoder devient garantie par construction ; toute contrainte plus riche (imbrication, récursion) exigera la montée en puissance de la tranche 2." ] }, { @@ -890,7 +952,9 @@ "Cette tranche etablit le passage **regle -> automate** : on ecrit la regle dans\n", "un formalisme compact (CFG algebrique, ou JSON Schema), et un compilateur la\n", "convertit en un automate utilisable par la pipeline `build_allowed_token_ids`.\n", - "" + "\n", + "\n", + "**Ce que le passage déclaratif achète — et coûte.** Écrire la grammaire au lieu de l'automate achète la réutilisation : un même moteur sert les dates, les objets JSON, tout schéma compilable. Il coûte la maîtrise fine : le DFA artisanal encodait exactement le langage visé, rien de plus ; un compilateur général introduit des **bords** (constructs ignorés, arbitrages erronés) qu'il faudra apprendre à lire dans les ensembles autorisés plutôt que dans le code." ] }, { @@ -910,7 +974,9 @@ "Pour notre usage, on l'implemente comme un dictionnaire de regles : chaque\n", "non-terminal a une **liste de productions**, chaque production est une **liste\n", "de symboles** (terminaux ou references a d'autres non-terminaux).\n", - "" + "\n", + "\n", + "**Pourquoi la CFG et non un DFA direct.** Le JSON est **imbriqué** : un objet contient des tableaux qui contiennent des objets — une imbrication non bornée qu'aucun automate fini ne peut compter. La grammaire algébrique est précisément la classe qui capture cette récursion structurelle avec un ensemble **fini** de règles. Le prix à payer : l'analyse devient plus coûteuse, et la conversion vers un masque par token, objet de la section suivante, n'est plus triviale." ] }, { @@ -1007,6 +1073,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-06", + "metadata": {}, + "source": [ + "### Lecture — 9 règles, 81 productions : la factorisation CFG\n", + "\n", + "L'expansement déterministe de la première production rend `'0000-00-00'` — chaque non-terminal choisit sa première alternative par défaut. La grammaire ISO tient en **9 règles** qui, une fois les alternatives énumérées, couvrent **81 productions**. C'est tout l'intérêt de la forme algébrique : plutôt qu'énumérer les dates acceptables (impossible), on factorise la structure — segments, séparateurs, alternatives de chiffres — et le cardinal émerge du calcul." + ] + }, { "cell_type": "markdown", "id": "fd50d12f", @@ -1026,7 +1102,9 @@ "un **ensemble de positions** dans la derivation en cours (items Earley). Le\n", "test d'acceptation d'un token rejoue toutes les derivations partielles\n", "coherentes avec le prefixe courant.\n", - "" + "\n", + "\n", + "**L'arbitrage pédagogique.** Le pire cas de la conversion CFG→DFA est exponentiel, et les grammaires issues de JSON Schema sont récursives à gauche dès la deuxième propriété (`Body -> Body ',' Kv`). Le pattern Earley « simplifié » retenu ici accepte ce compromis : exact au départ, défaillant sur certains préfixes entamés — une défaillance **documentée** (voir la mesure de parité plus bas), qui devient elle-même un objet d'étude plutôt qu'un angle mort." ] }, { @@ -1167,6 +1245,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-07", + "metadata": {}, + "source": [ + "### Lecture — parité CFG/DFA au départ, divergence documentée ensuite\n", + "\n", + "Au préfixe vide, la CFG autorise **10 tokens** — les mêmes dix chiffres que le DFA de la tranche 1, et l'organe de parité le confirme (`OK CFG et DFA agree au début`). Mais après consommation de `'2927-'`, l'ensemble retombe à **0 token** : l'Earley simplifié (« V2 ») n'arbitre plus correctement entre productions concurrentes une fois un préfixe entamé. Ce n'est pas un bug silencieux : la limitation est **documentée dans la sortie même** et renvoyée au Bilan — la pipeline pédagogique assume ses bords." + ] + }, { "cell_type": "markdown", "id": "a70f57d9", @@ -1188,7 +1276,9 @@ "`additionalProperties`) declenchent une **erreur explicite** : on documente\n", "honement le plafond atteignable, plutot que de mocker une couverture\n", "par defaut.\n", - "" + "\n", + "\n", + "**La philosophie du sous-ensemble.** Couvrir `type` de base, `properties`, `items` : c'est le noyau qui suffit à démontrer la chaîne complète — schéma déclaratif, grammaire, ensemble autorisé, masque. Ce qui reste dehors (`pattern`, `oneOf`, `anyOf`, `$ref`, bornes numériques) n'est pas omis par oubli : il est **énuméré** plus bas, construct par construct, pour que l'étudiant sache exactement où finit l'outil pédagogique et où commence la bibliothèque de production." ] }, { @@ -1347,7 +1437,9 @@ "\n", "Un ratio faible (< 1%) = beaucoup de tokens bloques = generation tres dirigee.\n", "Un ratio eleve (> 50%) = la grammaire est permissive.\n", - "" + "\n", + "\n", + "**Ce que la mesure va départager.** Le récit attendu d'une pipeline de masquage : un coût de compilation payé une fois, puis un surcoût par token faible et stable. Les trois mesures qui suivent (compilation, surcoût incrémental, ratio d'imposition) mettent ce récit à l'épreuve sur l'automate ISO — et le verdict à venir réserve une surprise : c'est le **second** terme du récit qui lâche." ] }, { @@ -1418,6 +1510,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-08", + "metadata": {}, + "source": [ + "### Lecture — la compilation s'amortit, le surcoût par token non\n", + "\n", + "La compilation initiale coûte **0.758 s** pour un ensemble autorisé de 10 tokens (0.01 % du vocabulaire). Mais chaque appel incrémental sur préfixe coûte en moyenne **1.158 s** (détail : 0.773 s à 1.272 s selon le préfixe) — le verdict affiché le dit crûment : la compilation est 0.7× plus **lente** qu'un appel incrémental, autrement dit le « fixé par token » espéré n'est pas au rendez-vous. La pipeline artisanale refait le travail d'arbitrage à chaque pas ; la section suivante mesure ce que coûte une implémentation qui ne le refait pas." + ] + }, { "cell_type": "markdown", "id": "a3f9b1c2", @@ -1428,7 +1530,9 @@ "**Pourquoi.** Le compilateur JSON Schema minimal de §3.3 **ignore** `pattern` (placeholder `a` pour toute string). Le carnet presente cette limite comme pedagogique -- et l'est -- mais #18212 §3 demande une comparaison avec **une bibliotheque de reference reellement installable**. C'est l'objet de cette section : installer `lm-format-enforcer` (pure-Python, RECOVERABLE-LOCAL, CPU-only, 0 GPU requis -- 0.11.3 verifie par `pip show`) et l'appliquer aux memes schemas que le compilateur minimal.\n", "\n", "**Verdict SOTA (regle H, axe 1) : RECOVERABLE-LOCAL resolu -> SOTA-OK.** La bibliotheque etait CPU-installable et absente de l'env local ; elle est maintenant presente (cf. cellule de comparaison ci-dessous). Le verdict passe de RECOVERABLE-MACHINE (faux) a SOTA-OK sur le point `pattern` qui etait la limite documentee du compilateur minimal.\n", - "" + "\n", + "\n", + "**Quand atteindre la bibliothèque.** La règle pratique qui se dégage : le compilateur minimal pour **comprendre** la chaîne complète (schéma → grammaire → ensemble → masque), lm-format-enforcer pour **produire** — dès qu'un `pattern`, un `oneOf` ou une borne numérique entre dans le schéma réel. La comparaison qui suit est menée sur les mêmes schémas, avec le même tokenizer, pour que l'écart mesure les moteurs et non les protocoles." ] }, { @@ -1557,6 +1661,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-09", + "metadata": {}, + "source": [ + "### Lecture — lm-format-enforcer : trois ordres de grandeur en dessous\n", + "\n", + "Sur les mêmes schémas, `lm-format-enforcer` compile le schéma User en **0.04 ms** et le regex ZIP en **0.30 ms** (100 itérations : 3.8 ms et 30.1 ms) — là où la pipeline minimale passe 758 ms et plus d'une seconde par token. Le détail regex montre aussi un comportement fin : après `'12345'`, l'ensemble autorisé est **vide** (`[]`, regex satisfaite, fin de chaîne) — une génération contrainte sait **terminer proprement**. Enfin, lmfe couvre `pattern`, `oneOf`, `anyOf`, `$ref` que le compilateur minimal ignore par choix pédagogique." + ] + }, { "cell_type": "code", "execution_count": 15, @@ -1729,6 +1843,16 @@ " torch.cuda.empty_cache()" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-10", + "metadata": {}, + "source": [ + "### Lecture — directivité n'est pas justesse : le tableau du modèle réel\n", + "\n", + "Le modèle réel (Qwen2.5-0.5B, CUDA, greedy) départage les trois modes. **Libre** : 24 pas, 0 % de forces — mais l'inventeur devient `\"Qwen\"` et l'âge `\"20\"`, dans du markdown JSON. **Minimal** : 7 pas, 86 % de forces — sur-contraint, la sortie casse (`'\"\"\"\"name\":aa}'`). **lmfe** : 19 pas, 11 % de forces — un JSON propre `{\"name\": \"Inventor\", \"age\": 25}`. Sur ZIP, le contraste est plus net encore : minimal meurt à 2 pas (`'{{'`, 100 % de forces) quand lmfe produit `'{\"zip\": \"75000\"}'` à 31 %. La leçon : ce n'est pas le **taux** de tokens forcés qui fait la validité, c'est la **justesse de l'ensemble autorisé**." + ] + }, { "cell_type": "markdown", "id": "fb5a2030", @@ -1746,7 +1870,9 @@ "- Le compilateur JSON Schema couvre un sous-ensemble rectangulaire (primitives, object avec properties, array simple, enum). Les constructs non couverts (`oneOf`, `anyOf`, `$ref`) levent `JSONSchemaCompilerError` explicitement.\n", "- Le cout de compilation est lineaire en |V|. Pour Qwen-2.5-0.5B (~152k tokens), une compilation prend ~2-3 secondes en CPU. C'est acceptable pour un appel ponctuel, prohibitif pour une regen par token. La section 3.5 mesure ce cout en generation reelle.\n", "\n", - "**Comparaison en generation reelle (§3.5)** : branche au vrai modele Qwen2.5-0.5B-Instruct, le compilateur minimal se bloque ou produit du JSON malforme (sortie `\"\"\"\"name\":aa}` sur le schema User ; arret apres `{{` sur le schema ZIP, dont `pattern` est ignore), tandis que `lm-format-enforcer` produit un JSON valide dans les deux cas en ne forcant qu'une minorite des pas de decodage (11-31 %) : le masque corrige le modele la ou il derive du schema (noms de champs, digits du code postal) sans le contraindre la ou il est deja conforme." + "**Comparaison en generation reelle (§3.5)** : branche au vrai modele Qwen2.5-0.5B-Instruct, le compilateur minimal se bloque ou produit du JSON malforme (sortie `\"\"\"\"name\":aa}` sur le schema User ; arret apres `{{` sur le schema ZIP, dont `pattern` est ignore), tandis que `lm-format-enforcer` produit un JSON valide dans les deux cas en ne forcant qu'une minorite des pas de decodage (11-31 %) : le masque corrige le modele la ou il derive du schema (noms de champs, digits du code postal) sans le contraindre la ou il est deja conforme.\n", + "\n", + "**Synthèse d'étage.** La tranche 2 a déplacé la garantie : de « un automate écrit à la main pour un format » vers « une chaîne déclarative compilée depuis une spécification ». Elle a aussi déplacé le lieu de la vigilance : un DFA artisanal échoue bruyamment, un compilateur général échoue **silencieusement** — il compile, et le contrat se creuse. D'où les deux exemples guidés qui suivent : apprendre à débusquer les contrats creux dans les ensembles autorisés eux-mêmes." ] }, { @@ -1768,7 +1894,9 @@ "\n", "**Ce que l'etudiant doit reproduire** : ouvrir la cellule code, observer l'execution\n", "du code, et etendre le compilateur minimal qui supporte\n", - "`pattern` simple (chiffres/lettres) au prix d'une approximation." + "`pattern` simple (chiffres/lettres) au prix d'une approximation.\n", + "\n", + "Le principe de l'exemple : **faire parler l'ensemble autorisé** plutôt que le code de retour. Le schéma compile, la génération se déroule — et pourtant le contrat promis (5 chiffres exactement) n'est pas tenu. C'est le motif de vigilance structurant de toute la fin de carnet : la preuve d'une contrainte vit dans le cardinal **et le contenu** de l'ensemble autorisé à chaque pas, jamais dans le seul succès de la compilation." ] }, { @@ -1859,6 +1987,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-11", + "metadata": {}, + "source": [ + "### Lecture — un compilateur permissif est un contrat creux\n", + "\n", + "La compilation du schéma ZIP **réussit** (axiome ``, 4 règles) alors même que `pattern` est ignoré : l'ensemble autorisé compte 11 tokens (`'\"'`, `'{'`, `'}'`, `'{{'`, `'\"\"'`, `'}}'`, `'\"}'`, …) soit 0.0073 % du vocabulaire. Le cardinal paraît strict ; le contenu est creux — la règle `string` génère un placeholder et les 5 chiffres consécutifs ne sont jamais validés. La sortie le dit : la sortie compilée est **permissive**. Un contrat qui compile n'est pas un contrat qui contraint : il faut lire l'ensemble autorisé, pas seulement le code de retour." + ] + }, { "cell_type": "markdown", "id": "abfeb17d", @@ -1878,7 +2016,9 @@ "**Ce que l'etudiant doit faire** : sur un exemple de schema qu'il concoit\n", "lui-meme, identifier **un** de ces flags que son schema presuppose mais que\n", "notre compilateur ignore, puis estimer le taux d'erreur sur 5 generations\n", - "concretes (en utilisant `constrained_generate_date` ou equivalent)." + "concretes (en utilisant `constrained_generate_date` ou equivalent).\n", + "\n", + "L'exemple inverse du précédent : ici le schéma **accepte un sous-ensemble insuffisant** — la grammaire produite est plus large que le schéma ne le spécifie. Le défaut se mesure de la même façon : énumérer les tokens autorisés à un préfixe donné et confronter cette liste à ce que le schéma exigerait. Deux échecs opposés (trop strict, trop permissif), un même instrument de diagnostic." ] }, { @@ -1956,6 +2096,16 @@ "" ] }, + { + "cell_type": "markdown", + "id": "d3c-lect-12", + "metadata": {}, + "source": [ + "### Lecture — 19 constructs manquants, et le plus dangereux est silencieux\n", + "\n", + "Le relevé couvre 5 types pour **19 constructs non couverts** : `object` en perd 4 (`additionalProperties`, `patternProperties`, `minProperties`, `maxProperties`), `array` 4, `string` 3, `integer` 4, `number` 4. La démonstration finale choisit le pire d'entre eux : la compilation **réussit** alors que `additionalProperties: false` a été ignoré — l'utilisateur peut insérer des propriétés non déclarées **silencieusement**. Contrairement à un crash, cette défaillance ne se voit pas dans la sortie générée : elle se voit seulement dans l'écart entre schéma demandé et grammaire produite." + ] + }, { "cell_type": "markdown", "id": "05c7f507", @@ -1971,7 +2121,7 @@ " que les chiffres.\n", "\n", "La sortie attendue est une mesure `imposed_ratio` pour chacune, avec\n", - "interpretation de l'ecart en termes de **directivite de generation**.\n", + "explication de l'ecart en termes de **directivite de generation**.\n", "\n", "**Ce que l'etudiant peut faire** : reproduire la comparaison avec un\n", "troisieme schema (par exemple `{\"type\": \"boolean\"}`) et verifier que le\n", @@ -2020,6 +2170,16 @@ " print(f\"\\nLa grammaire stricte force {ratio_permissive/ratio_strict:.1f}x plus la generation.\")\n", "" ] + }, + { + "cell_type": "markdown", + "id": "d3c-lect-13", + "metadata": {}, + "source": [ + "### Lecture — strict ou permissif : deux cardinaux, même échelle\n", + "\n", + "La grammaire permissive (`string`) n'autorise que **2 tokens** (0.00 % du vocabulaire) et la stricte (`integer`) **10 tokens** (0.01 %) : la stricte force cinq fois plus, ce que le verdict chiffre en « 0.2× plus la génération ». Mais le point le plus utile est l'ordre de grandeur commun : même la grammaire **la plus permissive** de ce corpus ne laisse qu'une fraction infime du vocabulaire. À l'échelle de 151 643 tokens, toute grammaire est directive — la comparaison **relative** entre schémas est donc le bon instrument, pas le ratio absolu." + ] } ], "metadata": {