From 11db53b4d4bd2d9a1a182db41c342d36e9cd0b1f Mon Sep 17 00:00:00 2001 From: jsboige Date: Wed, 7 Oct 2026 02:35:59 +0200 Subject: [PATCH] feat(genai,#18574): muscle les trois exemples guides de 03_Structured_Outputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les trois exemples guidés crédités (film, extracteur d'événements, analyseur de produits) affichaient leur résultat sans jamais le vérifier. Chacun reçoit une cellule de vérification placée APRÈS son code, et une cellule « Interprétation » qui lit la sortie réelle. Le code et le crédit de l'étudiante sont conservés à l'identique. - film : conformité au schéma vérifiée sur la réponse réelle (CONFORME) **et** sur un objet fabriqué (3 écarts : champ requis absent, type, énumération) — le contrôle discrimine au lieu d'approuver. - événements : les trois garde-fous sont vus en train de REJETER (date incohérente, titre vide, priorité hors énumération), avec un témoin valide qui passe. Le `loc` vide du rejet de date est la signature du @model_validator, pas un défaut. - produits : fidélité confrontée au texte source (prix, note, avis), et provenance des libellés mise au jour — `Marque` et `Stockage` sont INFÉRÉS, `puce` est renommé en `Processeur`. Ré-exécution : papermill, 19/19 cellules, 0 erreur, 96 s, sorties committées. Aucune source de code préexistante modifiée (invariant vérifié contre origin/main). See #18574 Co-Authored-By: Claude Sonnet 5.5 --- .../GenAI/Texte/03_Structured_Outputs.ipynb | 821 ++++++++++++------ 1 file changed, 547 insertions(+), 274 deletions(-) diff --git a/MyIA.AI.Notebooks/GenAI/Texte/03_Structured_Outputs.ipynb b/MyIA.AI.Notebooks/GenAI/Texte/03_Structured_Outputs.ipynb index aa94afa7a7..f4cbd97bca 100644 --- a/MyIA.AI.Notebooks/GenAI/Texte/03_Structured_Outputs.ipynb +++ b/MyIA.AI.Notebooks/GenAI/Texte/03_Structured_Outputs.ipynb @@ -3,19 +3,19 @@ { "cell_type": "code", "execution_count": 1, - "id": "5f896c0c", + "id": "c87d4657", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:09.782369Z", - "iopub.status.busy": "2026-09-29T22:53:09.781591Z", - "iopub.status.idle": "2026-09-29T22:53:09.806302Z", - "shell.execute_reply": "2026-09-29T22:53:09.804740Z" + "iopub.execute_input": "2026-10-07T00:32:11.863055Z", + "iopub.status.busy": "2026-10-07T00:32:11.862472Z", + "iopub.status.idle": "2026-10-07T00:32:11.870362Z", + "shell.execute_reply": "2026-10-07T00:32:11.868727Z" }, "papermill": { - "duration": 0.035343, - "end_time": "2026-09-29T22:53:09.807810", + "duration": 0.02118, + "end_time": "2026-10-07T00:32:11.872406+00:00", "exception": false, - "start_time": "2026-09-29T22:53:09.772467", + "start_time": "2026-10-07T00:32:11.851226+00:00", "status": "completed" }, "tags": [ @@ -33,10 +33,10 @@ "id": "87b89626", "metadata": { "papermill": { - "duration": 0.007305, - "end_time": "2026-09-29T22:53:09.822200", + "duration": 0.009498, + "end_time": "2026-10-07T00:32:11.895455+00:00", "exception": false, - "start_time": "2026-09-29T22:53:09.814895", + "start_time": "2026-10-07T00:32:11.885957+00:00", "status": "completed" }, "tags": [] @@ -65,10 +65,10 @@ "id": "f7fea02b", "metadata": { "papermill": { - "duration": 0.00707, - "end_time": "2026-09-29T22:53:09.835347", + "duration": 0.008015, + "end_time": "2026-10-07T00:32:11.912505+00:00", "exception": false, - "start_time": "2026-09-29T22:53:09.828277", + "start_time": "2026-10-07T00:32:11.904490+00:00", "status": "completed" }, "tags": [] @@ -103,27 +103,42 @@ "id": "33dfba62", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:09.854639Z", - "iopub.status.busy": "2026-09-29T22:53:09.853131Z", - "iopub.status.idle": "2026-09-29T22:53:13.020871Z", - "shell.execute_reply": "2026-09-29T22:53:13.019747Z" + "iopub.execute_input": "2026-10-07T00:32:11.935334Z", + "iopub.status.busy": "2026-10-07T00:32:11.934749Z", + "iopub.status.idle": "2026-10-07T00:32:38.993332Z", + "shell.execute_reply": "2026-10-07T00:32:38.990365Z" }, "papermill": { - "duration": 3.178872, - "end_time": "2026-09-29T22:53:13.022389", + "duration": 27.07056, + "end_time": "2026-10-07T00:32:38.994850+00:00", "exception": false, - "start_time": "2026-09-29T22:53:09.843517", + "start_time": "2026-10-07T00:32:11.924290+00:00", "status": "completed" }, "tags": [] }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "[notice] A new release of pip is available: 25.2 -> 26.2.1\n", + "[notice] To update, run: python.exe -m pip install --upgrade pip\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n", - ".env charge depuis: .env\n", + ".env charge depuis: .env\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "Client OpenAI initialisé !\n", "Modèle par défaut: gpt-5-mini\n" ] @@ -238,10 +253,10 @@ "id": "c75d5b7f", "metadata": { "papermill": { - "duration": 0.005277, - "end_time": "2026-09-29T22:53:13.034369", + "duration": 0.015224, + "end_time": "2026-10-07T00:32:39.021302+00:00", "exception": false, - "start_time": "2026-09-29T22:53:13.029092", + "start_time": "2026-10-07T00:32:39.006078+00:00", "status": "completed" }, "tags": [] @@ -276,10 +291,10 @@ "id": "7a7ea647", "metadata": { "papermill": { - "duration": 0.005281, - "end_time": "2026-09-29T22:53:13.046420", + "duration": 0.013064, + "end_time": "2026-10-07T00:32:39.050984+00:00", "exception": false, - "start_time": "2026-09-29T22:53:13.041139", + "start_time": "2026-10-07T00:32:39.037920+00:00", "status": "completed" }, "tags": [] @@ -296,16 +311,16 @@ "id": "34dbac5e", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:13.063899Z", - "iopub.status.busy": "2026-09-29T22:53:13.063899Z", - "iopub.status.idle": "2026-09-29T22:53:17.009820Z", - "shell.execute_reply": "2026-09-29T22:53:17.008689Z" + "iopub.execute_input": "2026-10-07T00:32:39.081144Z", + "iopub.status.busy": "2026-10-07T00:32:39.080497Z", + "iopub.status.idle": "2026-10-07T00:32:47.225783Z", + "shell.execute_reply": "2026-10-07T00:32:47.224178Z" }, "papermill": { - "duration": 3.957351, - "end_time": "2026-09-29T22:53:17.010829", + "duration": 8.162043, + "end_time": "2026-10-07T00:32:47.226989+00:00", "exception": false, - "start_time": "2026-09-29T22:53:13.053478", + "start_time": "2026-10-07T00:32:39.064946+00:00", "status": "completed" }, "tags": [] @@ -319,16 +334,16 @@ "{\n", " \"fruits\": [\n", " {\n", - " \"nom\": \"pomme\",\n", - " \"couleur\": \"rouge\"\n", + " \"nom\": \"Pomme\",\n", + " \"couleur\": \"Rouge\"\n", " },\n", " {\n", - " \"nom\": \"banane\",\n", - " \"couleur\": \"jaune\"\n", + " \"nom\": \"Banane\",\n", + " \"couleur\": \"Jaune\"\n", " },\n", " {\n", - " \"nom\": \"raisin\",\n", - " \"couleur\": \"violet\"\n", + " \"nom\": \"Kiwi\",\n", + " \"couleur\": \"Vert\"\n", " }\n", " ]\n", "}\n", @@ -356,10 +371,10 @@ "id": "67fff722", "metadata": { "papermill": { - "duration": 0.00809, - "end_time": "2026-09-29T22:53:17.026082", + "duration": 0.014185, + "end_time": "2026-10-07T00:32:47.248827+00:00", "exception": false, - "start_time": "2026-09-29T22:53:17.017992", + "start_time": "2026-10-07T00:32:47.234642+00:00", "status": "completed" }, "tags": [] @@ -404,10 +419,10 @@ "id": "389f24ec", "metadata": { "papermill": { - "duration": 0.007091, - "end_time": "2026-09-29T22:53:17.042276", + "duration": 0.00896, + "end_time": "2026-10-07T00:32:47.266331+00:00", "exception": false, - "start_time": "2026-09-29T22:53:17.035185", + "start_time": "2026-10-07T00:32:47.257371+00:00", "status": "completed" }, "tags": [] @@ -446,10 +461,10 @@ "id": "c087f6ab", "metadata": { "papermill": { - "duration": 0.007283, - "end_time": "2026-09-29T22:53:17.056695", + "duration": 0.010923, + "end_time": "2026-10-07T00:32:47.288126+00:00", "exception": false, - "start_time": "2026-09-29T22:53:17.049412", + "start_time": "2026-10-07T00:32:47.277203+00:00", "status": "completed" }, "tags": [] @@ -474,16 +489,16 @@ "id": "2cddb986", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:17.074846Z", - "iopub.status.busy": "2026-09-29T22:53:17.074318Z", - "iopub.status.idle": "2026-09-29T22:53:20.528170Z", - "shell.execute_reply": "2026-09-29T22:53:20.527170Z" + "iopub.execute_input": "2026-10-07T00:32:47.315965Z", + "iopub.status.busy": "2026-10-07T00:32:47.315131Z", + "iopub.status.idle": "2026-10-07T00:32:53.538135Z", + "shell.execute_reply": "2026-10-07T00:32:53.535511Z" }, "papermill": { - "duration": 3.464882, - "end_time": "2026-09-29T22:53:20.530170", + "duration": 6.242407, + "end_time": "2026-10-07T00:32:53.541504+00:00", "exception": false, - "start_time": "2026-09-29T22:53:17.065288", + "start_time": "2026-10-07T00:32:47.299097+00:00", "status": "completed" }, "tags": [] @@ -497,8 +512,8 @@ "\n", "Recette: Crêpes classiques\n", "Difficulté: facile\n", - "Temps: 30 minutes\n", - "Ingrédients: 250 g de farine, 3 œufs, 500 ml de lait, 2 cuillères à soupe de sucre (facultatif), 1 pincée de sel, 2 cuillères à soupe d'huile neutre ou 30 g de beurre fondu, 1 sachet de sucre vanillé ou 1 cuillère à café d'extrait de vanille (facultatif), Beurre pour la cuisson\n" + "Temps: 45 minutes\n", + "Ingrédients: 250 g de farine, 3 œufs, 500 ml de lait, 50 ml d'eau, 2 cuillères à soupe de sucre (facultatif), 1 pincée de sel, 30 g de beurre fondu ou 2 cuillères à soupe d'huile, 1 cuillère à soupe d'extrait de vanille ou de rhum (facultatif), beurre ou huile pour la cuisson\n" ] } ], @@ -542,10 +557,10 @@ "id": "20837137", "metadata": { "papermill": { - "duration": 0.004322, - "end_time": "2026-09-29T22:53:20.538491", + "duration": 0.011539, + "end_time": "2026-10-07T00:32:53.565097+00:00", "exception": false, - "start_time": "2026-09-29T22:53:20.534169", + "start_time": "2026-10-07T00:32:53.553558+00:00", "status": "completed" }, "tags": [] @@ -590,10 +605,10 @@ "id": "66f5fe01", "metadata": { "papermill": { - "duration": 0.005168, - "end_time": "2026-09-29T22:53:20.548604", + "duration": 0.011846, + "end_time": "2026-10-07T00:32:53.588051+00:00", "exception": false, - "start_time": "2026-09-29T22:53:20.543436", + "start_time": "2026-10-07T00:32:53.576205+00:00", "status": "completed" }, "tags": [] @@ -619,16 +634,16 @@ "id": "687c2ed4", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:20.561400Z", - "iopub.status.busy": "2026-09-29T22:53:20.561400Z", - "iopub.status.idle": "2026-09-29T22:53:28.193721Z", - "shell.execute_reply": "2026-09-29T22:53:28.192601Z" + "iopub.execute_input": "2026-10-07T00:32:53.619562Z", + "iopub.status.busy": "2026-10-07T00:32:53.618954Z", + "iopub.status.idle": "2026-10-07T00:33:07.188319Z", + "shell.execute_reply": "2026-10-07T00:33:07.186746Z" }, "papermill": { - "duration": 7.641646, - "end_time": "2026-09-29T22:53:28.195400", + "duration": 13.588062, + "end_time": "2026-10-07T00:33:07.189769+00:00", "exception": false, - "start_time": "2026-09-29T22:53:20.553754", + "start_time": "2026-10-07T00:32:53.601707+00:00", "status": "completed" }, "tags": [] @@ -640,8 +655,8 @@ "text": [ "Film: Interstellar (2014)\n", "Genre: science-fiction | Realisateur: Christopher Nolan\n", - "Acteurs: Matthew McConaughey, Anne Hathaway, Jessica Chastain, Michael Caine, Matt Damon, Casey Affleck, Mackenzie Foy, Bill Irwin, Ellen Burstyn\n", - "Resume: Dans un futur proche où la Terre devient de moins en moins habitable, un ancien pilote de la NASA, Cooper, rejoint une mission interstellaire à travers un trou de ver près de Saturne pour trouver une nouvelle planète habitable. Le film explore la relativité du temps, les sacrifices personnels et la force des liens familiaux alors que l'équipe affronte des mondes hostiles et des dilemmes scientifiques. La découverte finale lie l'amour paternel et la physique, permettant à la fille de Cooper, Murph, de résoudre un problème crucial pour sauver l'humanité.\n" + "Acteurs: Matthew McConaughey, Anne Hathaway, Jessica Chastain, Michael Caine, Matt Damon, Mackenzie Foy, Ellen Burstyn, John Lithgow, Wes Bentley, Bill Irwin\n", + "Resume: Sur une Terre en déclin, l'ancien pilote Cooper rejoint une mission spatiale qui traverse un trou de ver près de Saturne pour trouver des mondes habitables. Tandis que l'équipe explore des planètes hostiles et affronte les effets relativistes du temps, Cooper lutte pour préserver l'avenir de l'humanité tout en tentant de rester en contact avec sa fille Murph. Le film aborde des thèmes de sacrifice, d'amour familial et de science (gravité, relativité, trous noirs) dans une aventure de science-fiction visuellement ambitieuse.\n" ] } ], @@ -686,15 +701,102 @@ "print(f\"Resume: {film['resume']}\")" ] }, + { + "cell_type": "code", + "execution_count": 6, + "id": "369d7fa9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-07T00:33:07.215519Z", + "iopub.status.busy": "2026-10-07T00:33:07.214860Z", + "iopub.status.idle": "2026-10-07T00:33:07.226244Z", + "shell.execute_reply": "2026-10-07T00:33:07.224744Z" + }, + "papermill": { + "duration": 0.026345, + "end_time": "2026-10-07T00:33:07.227625+00:00", + "exception": false, + "start_time": "2026-10-07T00:33:07.201280+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reponse du modele : CONFORME au schema\n", + "\n", + "Objet fabrique : 3 ecart(s) detecte(s)\n", + " - champ requis absent : resume\n", + " - annee : type str au lieu de integer\n", + " - genre : 'sf' hors enum ['action', 'comedie', 'drame', 'science-fiction', 'thriller']\n" + ] + } + ], + "source": [ + "# Verification de la sortie (ajout a l'integration) : le mode strict garantit une\n", + "# structure CONFORME au schema. On le verifie au lieu de le supposer -- et on montre\n", + "# que le controle discrimine, en le faisant echouer sur un objet fabrique a la main.\n", + "def ecarts_au_schema(obj, schema):\n", + " \"\"\"Ecarts entre `obj` et `schema` (liste vide = conforme).\"\"\"\n", + " ecarts = []\n", + " for champ in schema.get(\"required\", []):\n", + " if champ not in obj:\n", + " ecarts.append(f\"champ requis absent : {champ}\")\n", + " for champ, spec in schema.get(\"properties\", {}).items():\n", + " if champ not in obj:\n", + " continue\n", + " valeur = obj[champ]\n", + " attendu = {\"string\": str, \"integer\": int, \"array\": list, \"object\": dict}.get(spec.get(\"type\"))\n", + " if attendu is not None and not isinstance(valeur, attendu):\n", + " ecarts.append(f\"{champ} : type {type(valeur).__name__} au lieu de {spec['type']}\")\n", + " if \"enum\" in spec and valeur not in spec[\"enum\"]:\n", + " ecarts.append(f\"{champ} : {valeur!r} hors enum {spec['enum']}\")\n", + " return ecarts\n", + "\n", + "\n", + "if film:\n", + " reels = ecarts_au_schema(film, film_schema)\n", + " print(\"Reponse du modele :\", \"CONFORME au schema\" if not reels else f\"{len(reels)} ecart(s)\")\n", + " for e in reels:\n", + " print(\" -\", e)\n", + "\n", + " # Temoin negatif : le controle discrimine-t-il ? On le fait echouer sur un objet fabrique.\n", + " film_fabrique = {**film, \"annee\": \"2014\", \"genre\": \"sf\"}\n", + " film_fabrique.pop(\"resume\", None)\n", + " fabriques = ecarts_au_schema(film_fabrique, film_schema)\n", + " print(f\"\\nObjet fabrique : {len(fabriques)} ecart(s) detecte(s)\")\n", + " for e in fabriques:\n", + " print(\" -\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "efc798a3", + "metadata": {}, + "source": [ + "### Interprétation : ce que la vérification du schéma garantit — et ce qu'elle ne garantit pas\n", + "\n", + "Le schéma manuel `film_schema` était déclaré avec `\"strict\": True` : six champs tous `required`, `genre` borné à cinq valeurs, `additionalProperties: False`. Le contrôle ci-dessus le **vérifie** au lieu de le supposer.\n", + "\n", + "**Sur la réponse réelle** : `CONFORME au schema` — les six champs sont présents, `annee` est bien un entier (`2014`, pas `\"2014\"`), et `genre` vaut `science-fiction`, une des cinq valeurs de l'énumération.\n", + "\n", + "**Sur l'objet fabriqué** : `3 ecart(s) detecte(s)` — un champ requis retiré (`resume`), un type forcé en chaîne (`annee`), une valeur hors énumération (`genre: 'sf'`). Le contrôle **discrimine** : il ne se contente pas d'approuver.\n", + "\n", + "> **Ce que la conformité au schéma ne dit pas.** Que `titre` vaille `Interstellar` et `annee` `2014` ne prouve pas que ces valeurs soient **fidèles** — seulement qu'elles respectent la forme attendue. Le modèle aurait pu écrire `1997` et rester parfaitement conforme. Vérifier la forme et vérifier le fond sont deux contrôles distincts ; le second est l'objet de l'exemple sur les produits, et l'exercice 3 en fait une mesure (taux de réussite sur N textes variés)." + ] + }, { "cell_type": "markdown", "id": "52ee18a7", "metadata": { "papermill": { - "duration": 0.005959, - "end_time": "2026-09-29T22:53:28.208734", + "duration": 0.01207, + "end_time": "2026-10-07T00:33:07.251552+00:00", "exception": false, - "start_time": "2026-09-29T22:53:28.202775", + "start_time": "2026-10-07T00:33:07.239482+00:00", "status": "completed" }, "tags": [] @@ -723,10 +825,10 @@ "id": "579d1d40", "metadata": { "papermill": { - "duration": 0.006697, - "end_time": "2026-09-29T22:53:28.221987", + "duration": 0.016796, + "end_time": "2026-10-07T00:33:07.279996+00:00", "exception": false, - "start_time": "2026-09-29T22:53:28.215290", + "start_time": "2026-10-07T00:33:07.263200+00:00", "status": "completed" }, "tags": [] @@ -746,20 +848,20 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "d90a5796", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:28.233632Z", - "iopub.status.busy": "2026-09-29T22:53:28.233632Z", - "iopub.status.idle": "2026-09-29T22:53:40.659956Z", - "shell.execute_reply": "2026-09-29T22:53:40.658510Z" + "iopub.execute_input": "2026-10-07T00:33:07.312385Z", + "iopub.status.busy": "2026-10-07T00:33:07.311853Z", + "iopub.status.idle": "2026-10-07T00:33:18.667742Z", + "shell.execute_reply": "2026-10-07T00:33:18.666099Z" }, "papermill": { - "duration": 12.433115, - "end_time": "2026-09-29T22:53:40.660961", + "duration": 11.37388, + "end_time": "2026-10-07T00:33:18.668912+00:00", "exception": false, - "start_time": "2026-09-29T22:53:28.227846", + "start_time": "2026-10-07T00:33:07.295032+00:00", "status": "completed" }, "tags": [] @@ -770,31 +872,31 @@ "output_type": "stream", "text": [ "Recette: Mousse au chocolat classique\n", - "Temps: 150 min | Difficulté: Facile\n", + "Temps: 180 min | Difficulté: Facile\n", "\n", "Description:\n", - "Mousse aérienne au chocolat noir, simple et rapide à préparer. Texture légère grâce aux blancs montés en neige, saveur intense de chocolat.\n", + "Mousse légère et onctueuse à base de chocolat noir et d'œufs, facile à préparer et à laisser prendre au réfrigérateur.\n", "\n", "Ingrédients:\n", - " - Chocolat noir (minimum 60% cacao): 200 g\n", - " - Beurre (facultatif pour plus d'onctuosité): 20 g\n", - " - Œufs (frais) - séparés: 4 (4 jaunes + 4 blancs)\n", - " - Sucre: 30 g\n", + " - Chocolat noir (70%): 200 g\n", + " - Œufs frais: 4 (séparés : 4 jaunes et 4 blancs)\n", + " - Sucre en poudre: 30 g\n", " - Sel: 1 pincée\n", - " - Extrait de vanille ou café soluble (facultatif): 1 cuillère à café\n", + " - Beurre (facultatif): 20 g\n", + " - Extrait de vanille (facultatif): 1 cuillère à café\n", + " - Cacao en poudre ou copeaux de chocolat (pour la déco): q.s.\n", "\n", "Étapes:\n", - " 1. Préparez le matériel : ramequins ou un grand saladier, un fouet, un saladier propre pour monter les blancs, et une casserole pour bain-marie.\n", - " 2. Cassez le chocolat en morceaux et faites-le fondre doucement au bain-marie avec le beurre (si utilisé). Remuez jusqu'à obtenir une texture lisse. Retirez du feu et laissez tiédir quelques minutes.\n", - " 3. Pendant ce temps, fouettez les jaunes d'œufs avec le sucre (et l'extrait de vanille ou le café si utilisé) jusqu'à ce que le mélange devienne légèrement pâle et un peu mousseux.\n", - " 4. Incorporez les jaunes sucrés au chocolat tiédi en mélangeant rapidement et délicatement pour homogénéiser.\n", - " 5. Dans un autre saladier parfaitement propre, montez les blancs d'œufs en neige ferme avec la pincée de sel. Ils doivent former des pics fermes mais sans être secs.\n", - " 6. Ajoutez une cuillerée ou deux de blancs montés au mélange chocolaté et incorporez vigoureusement pour détendre la préparation (cela facilite l'incorporation des autres blancs).\n", - " 7. Incorporez ensuite le reste des blancs en neige en trois fois, avec une spatule, en effectuant des mouvements enveloppants du bas vers le haut pour conserver l'aération. Ne pas trop mélanger.\n", - " 8. Répartissez la mousse dans des ramequins individuels ou versez-la dans un grand récipient. Lissez la surface si besoin.\n", - " 9. Placez au réfrigérateur au moins 2 heures (idéal 3-4 heures) pour que la mousse prenne et développe sa texture.\n", - " 10. Au moment de servir, vous pouvez décorer avec quelques copeaux de chocolat, cacao tamisé ou fruits rouges.\n", - " 11. Remarque sécurité : cette recette utilise des œufs crus. Utilisez des œufs très frais ou pasteurisés si vous avez des personnes à risque (femmes enceintes, jeunes enfants, personnes immunodéprimées). Conservez la mousse au réfrigérateur et consommez-la sous 48 heures.\n" + " 1. Préparer les ingrédients : séparer les jaunes des blancs d'œufs. Sortir le beurre si utilisé pour qu'il soit à température ambiante.\n", + " 2. Faire fondre le chocolat : casser le chocolat en morceaux et le faire fondre doucement au bain-marie (ou au micro-ondes par courtes impulsions en remuant entre chaque). Ajouter le beurre hors du feu et mélanger jusqu'à obtenir une texture lisse. Laisser tiédir légèrement.\n", + " 3. Travailler les jaunes (optionnel) : dans un bol, mélanger les jaunes d'œufs avec l'extrait de vanille. Vous pouvez incorporer un peu du sucre aux jaunes si vous préférez une mousse plus douce (facultatif).\n", + " 4. Incorporer les jaunes au chocolat : ajouter les jaunes au chocolat tiédi en mélangeant rapidement pour homogénéiser. Réserver.\n", + " 5. Monter les blancs : dans un grand saladier propre et sec, monter les blancs d'œufs avec la pincée de sel. Dès qu'ils commencent à mousser, ajouter progressivement le sucre en continuant de battre jusqu'à obtenir des blancs fermes et brillants (pics souples à fermes selon la texture souhaitée).\n", + " 6. Tempérer la préparation au chocolat : pour éviter de retomber les blancs, incorporer d'abord une petite cuillerée de blancs montés au mélange chocolat/jaunes et mélanger vigoureusement pour l'assouplir.\n", + " 7. Incorporer les blancs restants : ajouter le reste des blancs en trois fois en les incorporant délicatement à l'aide d'une Maryse ou d'une spatule, par mouvements de bas en haut, en tournant le bol pour conserver un maximum d'air.\n", + " 8. Répartir et lisser : verser la mousse obtenue dans des verrines ou un grand plat. Lisser le dessus si besoin.\n", + " 9. Réfrigérer : couvrir et laisser prendre au réfrigérateur au minimum 2 heures, idéalement 3 à 4 heures pour une texture bien ferme.\n", + " 10. Servir : saupoudrer de cacao en poudre ou décorer de copeaux de chocolat juste avant de servir. Déguster bien frais.\n" ] } ], @@ -847,10 +949,10 @@ "id": "faea09b0", "metadata": { "papermill": { - "duration": 0.006657, - "end_time": "2026-09-29T22:53:40.674520", + "duration": 0.012208, + "end_time": "2026-10-07T00:33:18.690397+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.667863", + "start_time": "2026-10-07T00:33:18.678189+00:00", "status": "completed" }, "tags": [] @@ -900,10 +1002,10 @@ "id": "ed4f3286", "metadata": { "papermill": { - "duration": 0.006722, - "end_time": "2026-09-29T22:53:40.686332", + "duration": 0.011676, + "end_time": "2026-10-07T00:33:18.713388+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.679610", + "start_time": "2026-10-07T00:33:18.701712+00:00", "status": "completed" }, "tags": [] @@ -927,20 +1029,20 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "2af530e9", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:40.699795Z", - "iopub.status.busy": "2026-09-29T22:53:40.699795Z", - "iopub.status.idle": "2026-09-29T22:53:40.722070Z", - "shell.execute_reply": "2026-09-29T22:53:40.721471Z" + "iopub.execute_input": "2026-10-07T00:33:18.738091Z", + "iopub.status.busy": "2026-10-07T00:33:18.737506Z", + "iopub.status.idle": "2026-10-07T00:33:18.752142Z", + "shell.execute_reply": "2026-10-07T00:33:18.750204Z" }, "papermill": { - "duration": 0.029709, - "end_time": "2026-09-29T22:53:40.722070", + "duration": 0.030361, + "end_time": "2026-10-07T00:33:18.753636+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.692361", + "start_time": "2026-10-07T00:33:18.723275+00:00", "status": "completed" }, "tags": [] @@ -1001,15 +1103,99 @@ "print(f\"Participants: {', '.join(evenement.participants)}\")" ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2e0428fd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-07T00:33:18.778598Z", + "iopub.status.busy": "2026-10-07T00:33:18.777911Z", + "iopub.status.idle": "2026-10-07T00:33:18.787966Z", + "shell.execute_reply": "2026-10-07T00:33:18.786351Z" + }, + "papermill": { + "duration": 0.024422, + "end_time": "2026-10-07T00:33:18.789470+00:00", + "exception": false, + "start_time": "2026-10-07T00:33:18.765048+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ACCEPTE temoin valide (evenement posterieur a la reference)\n", + " REJETE date_evenement ANTERIEURE a date_reference -> [] : Value error, date_evenement 2026-03-23 anterieure a date_reference 2026-03-30\n", + " REJETE titre vide (validateur de champ non_vide) -> ['titre'] : Value error, champ texte vide\n", + " REJETE priorite hors enum -> ['priorite'] : Input should be 'haute', 'moyenne' or 'basse'\n" + ] + } + ], + "source": [ + "# Verification (ajout a l'integration) : l'organe distinctif de cet exemple est le\n", + "# validateur croise `date_coherente` -- or l'exemple ne montre que le chemin nominal.\n", + "# On le fait REJETER, et on garde un temoin valide qui, lui, passe.\n", + "from pydantic import ValidationError\n", + "\n", + "base = dict(\n", + " titre=\"Reunion de sprint\",\n", + " type_evenement=\"reunion\",\n", + " date_evenement=\"2026-04-06\",\n", + " priorite=\"moyenne\",\n", + " date_reference=\"2026-03-30\",\n", + " participants=[\"equipe dev\"],\n", + " description=\"Reunion de sprint a 14h\",\n", + ")\n", + "\n", + "cas = [\n", + " (\"temoin valide (evenement posterieur a la reference)\", base),\n", + " (\"date_evenement ANTERIEURE a date_reference\", {**base, \"date_evenement\": \"2026-03-23\"}),\n", + " (\"titre vide (validateur de champ non_vide)\", {**base, \"titre\": \" \"}),\n", + " (\"priorite hors enum\", {**base, \"priorite\": \"urgentissime\"}),\n", + "]\n", + "for libelle, champs in cas:\n", + " try:\n", + " EvenementExtrait(**champs)\n", + " print(f\" ACCEPTE {libelle}\")\n", + " except ValidationError as exc:\n", + " err = exc.errors()[0]\n", + " print(f\" REJETE {libelle} -> {list(err['loc'])} : {err['msg']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "27afc0ec", + "metadata": {}, + "source": [ + "### Interprétation : les trois rejets, et ce que le `loc` de chacun raconte\n", + "\n", + "L'exemple ci-dessus n'exerçait que le chemin nominal. Les quatre cas soumis au même modèle montrent ses trois garde-fous **en train de refuser** :\n", + "\n", + "| Cas | Verdict | Où l'erreur est localisée (`loc`) |\n", + "|---|---|---|\n", + "| témoin valide (04-06 postérieur à 03-30) | **ACCEPTE** | — |\n", + "| `date_evenement` antérieure à `date_reference` | REJETE | `[]` — erreur **de modèle** |\n", + "| `titre` réduit à des espaces | REJETE | `['titre']` — validateur **de champ** |\n", + "| `priorite` hors énumération | REJETE | `['priorite']` — contrainte **de type** |\n", + "\n", + "Le `loc` vide du deuxième cas n'est pas un défaut d'affichage : c'est la signature d'un `@model_validator(mode=\"after\")`, qui juge l'objet **dans son ensemble** — ici une incohérence entre deux champs — et ne peut donc désigner aucun champ unique. Les deux autres erreurs portent un `loc` de champ parce qu'elles sont locales. Cette distinction est ce qui permet, en production, de router une erreur vers le champ à corriger plutôt que vers l'objet entier.\n", + "\n", + "> **Témoin négatif** : sans le premier cas, on ne saurait pas si le modèle rejette *tout* — quatre rejets sur quatre ne prouveraient rien. C'est la présence d'un cas **accepté** qui rend les trois rejets significatifs." + ] + }, { "cell_type": "markdown", "id": "exemple-guide-validation", "metadata": { "papermill": { - "duration": 0.00555, - "end_time": "2026-09-29T22:53:40.734019", + "duration": 0.011929, + "end_time": "2026-10-07T00:33:18.813521+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.728469", + "start_time": "2026-10-07T00:33:18.801592+00:00", "status": "completed" }, "tags": [] @@ -1028,20 +1214,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "exemple-guide-validation-code", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:40.745103Z", - "iopub.status.busy": "2026-09-29T22:53:40.745103Z", - "iopub.status.idle": "2026-09-29T22:53:40.768909Z", - "shell.execute_reply": "2026-09-29T22:53:40.767828Z" + "iopub.execute_input": "2026-10-07T00:33:18.842466Z", + "iopub.status.busy": "2026-10-07T00:33:18.841734Z", + "iopub.status.idle": "2026-10-07T00:33:18.871196Z", + "shell.execute_reply": "2026-10-07T00:33:18.869786Z" }, "papermill": { - "duration": 0.031023, - "end_time": "2026-09-29T22:53:40.769916", + "duration": 0.045768, + "end_time": "2026-10-07T00:33:18.872516+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.738893", + "start_time": "2026-10-07T00:33:18.826748+00:00", "status": "completed" }, "tags": [] @@ -1051,7 +1237,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "1. Le JSON schema expose les contraintes (enums) :\n", + "1. Le JSON schema expose les contraintes (enums) :" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", " priorite : ['haute', 'moyenne', 'basse']\n", " type : ['reunion', 'deadline', 'livraison', 'presentation']\n", " date : string / date\n", @@ -1157,10 +1350,10 @@ "id": "27da427a", "metadata": { "papermill": { - "duration": 0.008445, - "end_time": "2026-09-29T22:53:40.783907", + "duration": 0.012529, + "end_time": "2026-10-07T00:33:18.897660+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.775462", + "start_time": "2026-10-07T00:33:18.885131+00:00", "status": "completed" }, "tags": [] @@ -1194,10 +1387,10 @@ "id": "7daf68cd", "metadata": { "papermill": { - "duration": 0.005632, - "end_time": "2026-09-29T22:53:40.795327", + "duration": 0.01361, + "end_time": "2026-10-07T00:33:18.923360+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.789695", + "start_time": "2026-10-07T00:33:18.909750+00:00", "status": "completed" }, "tags": [] @@ -1220,10 +1413,10 @@ "id": "ede4a8a9", "metadata": { "papermill": { - "duration": 0.007096, - "end_time": "2026-09-29T22:53:40.810942", + "duration": 0.013727, + "end_time": "2026-10-07T00:33:18.949898+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.803846", + "start_time": "2026-10-07T00:33:18.936171+00:00", "status": "completed" }, "tags": [] @@ -1234,20 +1427,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "id": "94541bbc", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:40.825180Z", - "iopub.status.busy": "2026-09-29T22:53:40.824174Z", - "iopub.status.idle": "2026-09-29T22:53:48.088999Z", - "shell.execute_reply": "2026-09-29T22:53:48.087341Z" + "iopub.execute_input": "2026-10-07T00:33:18.981254Z", + "iopub.status.busy": "2026-10-07T00:33:18.980731Z", + "iopub.status.idle": "2026-10-07T00:33:25.217755Z", + "shell.execute_reply": "2026-10-07T00:33:25.216154Z" }, "papermill": { - "duration": 7.271139, - "end_time": "2026-09-29T22:53:48.089521", + "duration": 6.254356, + "end_time": "2026-10-07T00:33:25.219361+00:00", "exception": false, - "start_time": "2026-09-29T22:53:40.818382", + "start_time": "2026-10-07T00:33:18.965005+00:00", "status": "completed" }, "tags": [] @@ -1351,10 +1544,10 @@ "id": "3146b70d", "metadata": { "papermill": { - "duration": 0.006143, - "end_time": "2026-09-29T22:53:48.103790", + "duration": 0.011434, + "end_time": "2026-10-07T00:33:25.248561+00:00", "exception": false, - "start_time": "2026-09-29T22:53:48.097647", + "start_time": "2026-10-07T00:33:25.237127+00:00", "status": "completed" }, "tags": [] @@ -1393,10 +1586,10 @@ "id": "14937da0", "metadata": { "papermill": { - "duration": 0.009131, - "end_time": "2026-09-29T22:53:48.119725", + "duration": 0.009636, + "end_time": "2026-10-07T00:33:25.270556+00:00", "exception": false, - "start_time": "2026-09-29T22:53:48.110594", + "start_time": "2026-10-07T00:33:25.260920+00:00", "status": "completed" }, "tags": [] @@ -1428,10 +1621,10 @@ "id": "86b38391", "metadata": { "papermill": { - "duration": 0.007567, - "end_time": "2026-09-29T22:53:48.136247", + "duration": 0.010699, + "end_time": "2026-10-07T00:33:25.292971+00:00", "exception": false, - "start_time": "2026-09-29T22:53:48.128680", + "start_time": "2026-10-07T00:33:25.282272+00:00", "status": "completed" }, "tags": [] @@ -1454,10 +1647,10 @@ "id": "b905e871", "metadata": { "papermill": { - "duration": 0.011027, - "end_time": "2026-09-29T22:53:48.155421", + "duration": 0.011206, + "end_time": "2026-10-07T00:33:25.313162+00:00", "exception": false, - "start_time": "2026-09-29T22:53:48.144394", + "start_time": "2026-10-07T00:33:25.301956+00:00", "status": "completed" }, "tags": [] @@ -1468,20 +1661,20 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "id": "ae5e1740", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:48.177675Z", - "iopub.status.busy": "2026-09-29T22:53:48.177168Z", - "iopub.status.idle": "2026-09-29T22:53:55.523405Z", - "shell.execute_reply": "2026-09-29T22:53:55.522240Z" + "iopub.execute_input": "2026-10-07T00:33:25.333404Z", + "iopub.status.busy": "2026-10-07T00:33:25.333091Z", + "iopub.status.idle": "2026-10-07T00:33:30.434371Z", + "shell.execute_reply": "2026-10-07T00:33:30.432990Z" }, "papermill": { - "duration": 7.356933, - "end_time": "2026-09-29T22:53:55.524569", + "duration": 5.111651, + "end_time": "2026-10-07T00:33:30.435642+00:00", "exception": false, - "start_time": "2026-09-29T22:53:48.167636", + "start_time": "2026-10-07T00:33:25.323991+00:00", "status": "completed" }, "tags": [] @@ -1494,17 +1687,19 @@ "=== ÉVALUATION STRUCTURÉE ===\n", "\n", "Score: 85/100\n", - "Commentaire: Phrase correcte, claire et factuelle : elle affirme simplement que Python est un langage de programmation populaire. La formulation est grammaticalement juste et concise. Pour être plus informative et précise, on pourrait indiquer en quoi ou auprès de qui il est populaire (par ex. développeurs web, data science) ou apporter une source/mesure (classements, statistiques).\n", + "Commentaire: Phrase courte, claire et grammaticalement correcte. L'énoncé est factuel et facilement compréhensible pour un large public. En revanche, il reste très général : il n'explique pas en quoi Python est populaire, dans quels domaines, ni sur quelles données s'appuie l'affirmation. Selon l'usage attendu (titre, introduction, description technique, etc.), on peut la conserver telle quelle ou la développer pour apporter précision et valeur ajoutée.\n", "\n", "Points forts:\n", + " + Clarté et simplicité\n", " + Grammaticalement correcte et bien ponctuée\n", - " + Clarté et concision — le message est immédiatement compréhensible\n", - " + Ton neutre et factuel\n", + " + Affirmation factuelle et neutre, accessible à un large public\n", + " + Convient comme phrase d'accroche ou résumé très court\n", "\n", "Points d'amélioration:\n", - " - Trop générale — manque de contexte (pour quel public ou quel usage ?)\n", - " - Aucune preuve ou précision (classement, domaines d'utilisation, exemples)\n", - " - Formulation peu nuancée : «populaire» est vague ; on pourrait préciser le degré («très populaire») ou citer des sources\n" + " - Trop générale — préciser pourquoi Python est populaire (lisibilité, écosystème, bibliothèques…)\n", + " - Donner des exemples de domaines d'utilisation (data science, web, scripting, IA…)\n", + " - Éventuellement appuyer l'affirmation par des chiffres ou classements si une preuve est nécessaire\n", + " - Adapter le niveau de détail au contexte (titre vs paragraphe explicatif)\n" ] } ], @@ -1583,10 +1778,10 @@ "id": "266958f3", "metadata": { "papermill": { - "duration": 0.007964, - "end_time": "2026-09-29T22:53:55.542721", + "duration": 0.008561, + "end_time": "2026-10-07T00:33:30.455925+00:00", "exception": false, - "start_time": "2026-09-29T22:53:55.534757", + "start_time": "2026-10-07T00:33:30.447364+00:00", "status": "completed" }, "tags": [] @@ -1620,10 +1815,10 @@ "id": "33fad4a5", "metadata": { "papermill": { - "duration": 0.009346, - "end_time": "2026-09-29T22:53:55.559773", + "duration": 0.010599, + "end_time": "2026-10-07T00:33:30.475789+00:00", "exception": false, - "start_time": "2026-09-29T22:53:55.550427", + "start_time": "2026-10-07T00:33:30.465190+00:00", "status": "completed" }, "tags": [] @@ -1645,10 +1840,10 @@ "id": "635ac7ba", "metadata": { "papermill": { - "duration": 0.008303, - "end_time": "2026-09-29T22:53:55.578792", + "duration": 0.011697, + "end_time": "2026-10-07T00:33:30.499018+00:00", "exception": false, - "start_time": "2026-09-29T22:53:55.570489", + "start_time": "2026-10-07T00:33:30.487321+00:00", "status": "completed" }, "tags": [] @@ -1663,20 +1858,20 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 13, "id": "e2743769", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:53:55.603362Z", - "iopub.status.busy": "2026-09-29T22:53:55.602847Z", - "iopub.status.idle": "2026-09-29T22:54:02.630710Z", - "shell.execute_reply": "2026-09-29T22:54:02.629020Z" + "iopub.execute_input": "2026-10-07T00:33:30.522193Z", + "iopub.status.busy": "2026-10-07T00:33:30.521609Z", + "iopub.status.idle": "2026-10-07T00:33:36.397030Z", + "shell.execute_reply": "2026-10-07T00:33:36.395539Z" }, "papermill": { - "duration": 7.042893, - "end_time": "2026-09-29T22:54:02.632290", + "duration": 5.888403, + "end_time": "2026-10-07T00:33:36.398492+00:00", "exception": false, - "start_time": "2026-09-29T22:53:55.589397", + "start_time": "2026-10-07T00:33:30.510089+00:00", "status": "completed" }, "tags": [] @@ -1693,27 +1888,27 @@ "Priorité: haute\n", "\n", "Résumé:\n", - "Utilisateur apprécie l'application mais le bouton d'export ne fonctionne plus depuis la dernière mise à jour et l'interface mobile est jugée confuse.\n", + "Utilisateur satisfait de l'application en général mais le bouton d'export est inopérant depuis la dernière mise à jour, rendant la situation urgente pour son travail ; il signale aussi une interface mobile confuse.\n", "\n", "Fonctionnalités mentionnées:\n", - " • Bouton d'export\n", - " • Interface mobile\n", - " • Application (usage général)\n", + " • bouton d'export\n", + " • interface mobile\n", "\n", "Bugs détectés:\n", - " 🐛 Le bouton d'export est inopérant depuis la dernière mise à jour — l'utilisateur clique plusieurs fois et rien ne se passe\n", + " 🐛 bouton d'export inopérant depuis la dernière mise à jour (aucune action lors du clic)\n", + " 🐛 problème reproductible malgré plusieurs tentatives de l'utilisateur\n", "\n", "Suggestions:\n", - " 💡 Corriger le dysfonctionnement du bouton d'export en priorité\n", - " 💡 Améliorer la clarté et l'ergonomie de l'interface mobile\n", + " 💡 corriger le fonctionnement du bouton d'export\n", + " 💡 améliorer la clarté/ergonomie de l'interface mobile\n", "\n", "Actions recommandées:\n", - " → Reproduire le bug sur les environnements de test avec la version de la dernière mise à jour\n", - " → Récupérer les informations complémentaires auprès de l'utilisateur (version de l'app, OS, modèle de téléphone, étapes exactes, éventuels messages d'erreur)\n", - " → Consulter les logs et ajouter du logging côté export pour identifier la cause\n", - " → Déployer un correctif / hotfix pour le bouton d'export si la cause est critique\n", - " → Prévoir une revue UX de l'interface mobile et prioriser des améliorations ergonomiques\n", - " → Communiquer un message clair à l'utilisateur (accusé de réception, délai estimé de correction)\n" + " → reproduire le bug sur les environnements concernés (versions mobile et backend), collecter logs et erreurs\n", + " → prioriser un correctif urgent compte tenu de l'impact sur le travail de l'utilisateur\n", + " → publier une mise à jour corrective et vérifier le QA avant diffusion\n", + " → fournir une solution de contournement temporaire (ex : export via interface web ou endpoint API) et informer l'utilisateur\n", + " → prévoir une revue ergonomique de l'interface mobile et implémenter améliorations pour la rendre moins confuse\n", + " → communiquer au client l'état d'avancement et les délais estimés\n" ] } ], @@ -1771,10 +1966,10 @@ "id": "7a57073e", "metadata": { "papermill": { - "duration": 0.009637, - "end_time": "2026-09-29T22:54:02.651853", + "duration": 0.014319, + "end_time": "2026-10-07T00:33:36.426460+00:00", "exception": false, - "start_time": "2026-09-29T22:54:02.642216", + "start_time": "2026-10-07T00:33:36.412141+00:00", "status": "completed" }, "tags": [] @@ -1810,10 +2005,10 @@ "id": "68eb1ada", "metadata": { "papermill": { - "duration": 0.008379, - "end_time": "2026-09-29T22:54:02.668804", + "duration": 0.026807, + "end_time": "2026-10-07T00:33:36.467540+00:00", "exception": false, - "start_time": "2026-09-29T22:54:02.660425", + "start_time": "2026-10-07T00:33:36.440733+00:00", "status": "completed" }, "tags": [] @@ -1833,20 +2028,20 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "id": "e590981d", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:54:02.685932Z", - "iopub.status.busy": "2026-09-29T22:54:02.685932Z", - "iopub.status.idle": "2026-09-29T22:54:12.279293Z", - "shell.execute_reply": "2026-09-29T22:54:12.278051Z" + "iopub.execute_input": "2026-10-07T00:33:36.496306Z", + "iopub.status.busy": "2026-10-07T00:33:36.495762Z", + "iopub.status.idle": "2026-10-07T00:33:44.191189Z", + "shell.execute_reply": "2026-10-07T00:33:44.185396Z" }, "papermill": { - "duration": 9.603605, - "end_time": "2026-09-29T22:54:12.280475", + "duration": 7.711861, + "end_time": "2026-10-07T00:33:44.192714+00:00", "exception": false, - "start_time": "2026-09-29T22:54:02.676870", + "start_time": "2026-10-07T00:33:36.480853+00:00", "status": "completed" }, "tags": [] @@ -1859,7 +2054,7 @@ "Produit: MacBook Pro M4 (1999.0 EUR) note : 4.7\n", "Caractéristiques :\n", " Marque: Apple\n", - " Puce: M4 Pro\n", + " Processeur: Puce M4 Pro\n", " RAM: 18 Go\n", " Stockage: 512 Go SSD\n", " Écran: Liquid Retina XDR 14 pouces\n", @@ -1913,15 +2108,93 @@ " print(f\" {a.auteur}: {a.note}/5 : {a.commentaire}\")" ] }, + { + "cell_type": "code", + "execution_count": 15, + "id": "00bc2142", + "metadata": { + "execution": { + "iopub.execute_input": "2026-10-07T00:33:44.220579Z", + "iopub.status.busy": "2026-10-07T00:33:44.219882Z", + "iopub.status.idle": "2026-10-07T00:33:44.230489Z", + "shell.execute_reply": "2026-10-07T00:33:44.228921Z" + }, + "papermill": { + "duration": 0.026798, + "end_time": "2026-10-07T00:33:44.231858+00:00", + "exception": false, + "start_time": "2026-10-07T00:33:44.205060+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " prix : texte 1999.0 | extrait 1999.0 | ecart 0.0000 OK\n", + " note_moyenne : texte 4.7 | extrait 4.7 | ecart 0.0000 OK\n", + " avis : texte {'Alice': 5, 'Bob': 4} | extrait {'Alice': 5, 'Bob': 4} OK\n", + "\n", + " Caracteristiques absentes mot pour mot du texte (donc inferees) : ['Marque', 'Processeur', 'Stockage']\n" + ] + } + ], + "source": [ + "# Verification (ajout a l'integration) : le schema garantit la FORME des champs, jamais\n", + "# leur FIDELITE au texte source. On confronte chaque valeur extraite au texte, et on\n", + "# distingue ce qui est ECRIT de ce qui est INFERE.\n", + "import unicodedata\n", + "\n", + "\n", + "def sans_accents(s):\n", + " return \"\".join(c for c in unicodedata.normalize(\"NFD\", s.lower()) if unicodedata.category(c) != \"Mn\")\n", + "\n", + "\n", + "if produit:\n", + " for champ, attendu, extrait in [(\"prix\", 1999.0, produit.prix), (\"note_moyenne\", 4.7, produit.note_moyenne)]:\n", + " ecart = abs(extrait - attendu)\n", + " verdict = \"OK\" if ecart < 1e-6 else \"ECART\"\n", + " print(f\" {champ:13s} : texte {attendu} | extrait {extrait} | ecart {ecart:.4f} {verdict}\")\n", + "\n", + " avis_attendus = {\"Alice\": 5, \"Bob\": 4}\n", + " avis_extraits = {a.auteur: a.note for a in produit.avis}\n", + " verdict = \"OK\" if avis_extraits == avis_attendus else \"ECART\"\n", + " print(f\" avis : texte {avis_attendus} | extrait {avis_extraits} {verdict}\")\n", + "\n", + " # Le schema ne garantit PAS la provenance : un nom de caracteristique qui n'apparait\n", + " # nulle part dans le texte est une INFERENCE du modele, pas une citation.\n", + " texte_norm = sans_accents(texte_produit + \" \" + produit.nom)\n", + " inferees = [c.nom for c in produit.caracteristiques if sans_accents(c.nom) not in texte_norm]\n", + " print(f\"\\n Caracteristiques absentes mot pour mot du texte (donc inferees) : {inferees}\")" + ] + }, + { + "cell_type": "markdown", + "id": "74000b76", + "metadata": {}, + "source": [ + "### Interprétation : schéma valide, valeurs fidèles… et une caractéristique renommée\n", + "\n", + "Trois questions distinctes reçoivent ici leur réponse, dans cet ordre :\n", + "\n", + "1. **Le schéma est-il respecté ?** `prix: 1999.0` et `note_moyenne: 4.7` tombent dans les bornes déclarées (`gt=0` ; `ge=0, le=5`), et les deux avis portent une note entière de 1 à 5.\n", + "2. **Les valeurs sont-elles fidèles au texte ?** `prix`, `note_moyenne` et les deux avis (`Alice` 5/5, `Bob` 4/5) correspondent **exactement** à la source — c'est ce que mesurent les trois `OK`.\n", + "3. **D'où viennent les libellés ?** Le contrôle final liste les noms de caractéristiques **absents mot pour mot** du texte : `['Marque', 'Processeur', 'Stockage']`. Le texte dit « portable **Apple** » et « **puce** M4 Pro » — le modèle a donc **inféré** la marque et **renommé** `puce` en `Processeur`. Rien d'inventé, mais rien de recopié non plus.\n", + "\n", + "> **La leçon opérationnelle.** Un schéma strict garantit la **forme** ; il ne garantit ni la **fidélité** à la source, ni la **provenance** d'un libellé. Sur une extraction qui alimente une décision (tarification, routage, fiche produit), ces deux dernières vérifications sont celles qui coûtent — et elles ne s'obtiennent pas en durcissant le schéma, mais en **confrontant la sortie à son entrée**, comme ci-dessus." + ] + }, { "cell_type": "markdown", "id": "f609513e", "metadata": { "papermill": { - "duration": 0.005824, - "end_time": "2026-09-29T22:54:12.293642", + "duration": 0.011135, + "end_time": "2026-10-07T00:33:44.255395+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.287818", + "start_time": "2026-10-07T00:33:44.244260+00:00", "status": "completed" }, "tags": [] @@ -1934,20 +2207,20 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 16, "id": "f0f16b97", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:54:12.305310Z", - "iopub.status.busy": "2026-09-29T22:54:12.305310Z", - "iopub.status.idle": "2026-09-29T22:54:12.325624Z", - "shell.execute_reply": "2026-09-29T22:54:12.325125Z" + "iopub.execute_input": "2026-10-07T00:33:44.277898Z", + "iopub.status.busy": "2026-10-07T00:33:44.277431Z", + "iopub.status.idle": "2026-10-07T00:33:44.290624Z", + "shell.execute_reply": "2026-10-07T00:33:44.288884Z" }, "papermill": { - "duration": 0.027772, - "end_time": "2026-09-29T22:54:12.327055", + "duration": 0.026718, + "end_time": "2026-10-07T00:33:44.292139+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.299283", + "start_time": "2026-10-07T00:33:44.265421+00:00", "status": "completed" }, "tags": [] @@ -2000,10 +2273,10 @@ "id": "be89f6ec", "metadata": { "papermill": { - "duration": 0.005068, - "end_time": "2026-09-29T22:54:12.339141", + "duration": 0.012795, + "end_time": "2026-10-07T00:33:44.321104+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.334073", + "start_time": "2026-10-07T00:33:44.308309+00:00", "status": "completed" }, "tags": [] @@ -2019,10 +2292,10 @@ "id": "04217bb4", "metadata": { "papermill": { - "duration": 0.005415, - "end_time": "2026-09-29T22:54:12.350638", + "duration": 0.01435, + "end_time": "2026-10-07T00:33:44.353326+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.345223", + "start_time": "2026-10-07T00:33:44.338976+00:00", "status": "completed" }, "tags": [] @@ -2043,20 +2316,20 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 17, "id": "d27ef288", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:54:12.366082Z", - "iopub.status.busy": "2026-09-29T22:54:12.365082Z", - "iopub.status.idle": "2026-09-29T22:54:12.373874Z", - "shell.execute_reply": "2026-09-29T22:54:12.372601Z" + "iopub.execute_input": "2026-10-07T00:33:44.377971Z", + "iopub.status.busy": "2026-10-07T00:33:44.377429Z", + "iopub.status.idle": "2026-10-07T00:33:44.385111Z", + "shell.execute_reply": "2026-10-07T00:33:44.383717Z" }, "papermill": { - "duration": 0.019688, - "end_time": "2026-09-29T22:54:12.374874", + "duration": 0.021145, + "end_time": "2026-10-07T00:33:44.386517+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.355186", + "start_time": "2026-10-07T00:33:44.365372+00:00", "status": "completed" }, "tags": [] @@ -2102,10 +2375,10 @@ "id": "4a1a9b45", "metadata": { "papermill": { - "duration": 0.00566, - "end_time": "2026-09-29T22:54:12.385037", + "duration": 0.015398, + "end_time": "2026-10-07T00:33:44.416642+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.379377", + "start_time": "2026-10-07T00:33:44.401244+00:00", "status": "completed" }, "tags": [] @@ -2128,20 +2401,20 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 18, "id": "aa0e49fd", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:54:12.400787Z", - "iopub.status.busy": "2026-09-29T22:54:12.400259Z", - "iopub.status.idle": "2026-09-29T22:54:12.420105Z", - "shell.execute_reply": "2026-09-29T22:54:12.418933Z" + "iopub.execute_input": "2026-10-07T00:33:44.447628Z", + "iopub.status.busy": "2026-10-07T00:33:44.447026Z", + "iopub.status.idle": "2026-10-07T00:33:44.453659Z", + "shell.execute_reply": "2026-10-07T00:33:44.452701Z" }, "papermill": { - "duration": 0.02896, - "end_time": "2026-09-29T22:54:12.421176", + "duration": 0.023928, + "end_time": "2026-10-07T00:33:44.455168+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.392216", + "start_time": "2026-10-07T00:33:44.431240+00:00", "status": "completed" }, "tags": [] @@ -2185,10 +2458,10 @@ "id": "9c0a2bc7", "metadata": { "papermill": { - "duration": 0.005766, - "end_time": "2026-09-29T22:54:12.433238", + "duration": 0.01291, + "end_time": "2026-10-07T00:33:44.478926+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.427472", + "start_time": "2026-10-07T00:33:44.466016+00:00", "status": "completed" }, "tags": [] @@ -2209,20 +2482,20 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "id": "b7254803", "metadata": { "execution": { - "iopub.execute_input": "2026-09-29T22:54:12.446337Z", - "iopub.status.busy": "2026-09-29T22:54:12.446337Z", - "iopub.status.idle": "2026-09-29T22:54:12.466640Z", - "shell.execute_reply": "2026-09-29T22:54:12.466000Z" + "iopub.execute_input": "2026-10-07T00:33:44.507960Z", + "iopub.status.busy": "2026-10-07T00:33:44.507355Z", + "iopub.status.idle": "2026-10-07T00:33:44.517149Z", + "shell.execute_reply": "2026-10-07T00:33:44.515640Z" }, "papermill": { - "duration": 0.028694, - "end_time": "2026-09-29T22:54:12.467649", + "duration": 0.027032, + "end_time": "2026-10-07T00:33:44.518370+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.438955", + "start_time": "2026-10-07T00:33:44.491338+00:00", "status": "completed" }, "tags": [] @@ -2261,10 +2534,10 @@ "id": "d823731e", "metadata": { "papermill": { - "duration": 0.007391, - "end_time": "2026-09-29T22:54:12.481243", + "duration": 0.008565, + "end_time": "2026-10-07T00:33:44.544512+00:00", "exception": false, - "start_time": "2026-09-29T22:54:12.473852", + "start_time": "2026-10-07T00:33:44.535947+00:00", "status": "completed" }, "tags": [] @@ -2366,12 +2639,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.13.7" }, "papermill": { "default_parameters": {}, - "duration": 65.231777, - "end_time": "2026-09-29T22:54:12.945449", + "duration": 96.206021, + "end_time": "2026-10-07T00:33:45.238605+00:00", "environment_variables": {}, "exception": null, "input_path": "03_Structured_Outputs.ipynb", @@ -2379,7 +2652,7 @@ "parameters": { "BATCH_MODE": "true" }, - "start_time": "2026-09-29T22:53:07.713672", + "start_time": "2026-10-07T00:32:09.032584+00:00", "version": "2.7.0" } },