diff --git a/MyIA.AI.Notebooks/GenAI/Texte/23_Du_Notebook_A_L_Application.ipynb b/MyIA.AI.Notebooks/GenAI/Texte/23_Du_Notebook_A_L_Application.ipynb index 179e987a572..c21784d57a8 100644 --- a/MyIA.AI.Notebooks/GenAI/Texte/23_Du_Notebook_A_L_Application.ipynb +++ b/MyIA.AI.Notebooks/GenAI/Texte/23_Du_Notebook_A_L_Application.ipynb @@ -7,7 +7,7 @@ "source": [ "# 23. Du notebook a l'application - servir le RAG de la serie\n", "\n", - "**Navigation** : [<< Précédent](22c_DSPy_Prompt_Compile.ipynb) | [Index](README.md) | [Suivant >>](README.md)\n", + "**Navigation** : [<< Précédent](22c_DSPy_Prompt_Compile.ipynb) | [Index](README.md) | [Suivant >>](24_Generative_Agents_MiniMonde.ipynb)\n", "\n", "Les carnets precedents construisent des pipelines qui vivent dans le notebook : les fonctions, l'etat et les resultats partagent les memes cellules. Cette serie s'arrete la ou commence l'usage reel : **servir** un pipeline a d'autres utilisateurs qu'un lecteur de notebook.\n", "\n", diff --git a/MyIA.AI.Notebooks/GenAI/Texte/24_Generative_Agents_MiniMonde.ipynb b/MyIA.AI.Notebooks/GenAI/Texte/24_Generative_Agents_MiniMonde.ipynb new file mode 100644 index 00000000000..e29f4c6c48f --- /dev/null +++ b/MyIA.AI.Notebooks/GenAI/Texte/24_Generative_Agents_MiniMonde.ipynb @@ -0,0 +1,1678 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ec4e9e17-4e7c-4db9-91bf-d3b8f88b2381", + "metadata": { + "papermill": { + "duration": 0.003918, + "end_time": "2026-11-04T08:44:04.816641+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:04.812723+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Navigation** : [Index](README.md) | [<< Précédent](23_Du_Notebook_A_L_Application.ipynb) | [Suivant >>](README.md)\n", + "\n", + "# 24 — Agents génératifs : mémoire, réflexion et planification d'un mini-monde\n", + "\n", + "En 2023, Park et al. ont peuplé un petit village fictif, Smallville, de 25 agents\n", + "pilotés par un LLM. Chaque agent possède un **flux de mémoire** horodaté, une\n", + "**récupération** à trois composantes (fraîcheur, importance, pertinence), la capacité\n", + "de **réfléchir** (généraliser ses observations en pensées abstraites) et de **planifier**\n", + "sa journée. Sans aucune intervention humaine, l'information se propage : sur deux jours\n", + "de jeu, la connaissance de la soirée de Saint-Valentin passe de 1 agent sur 25 (4 %) à\n", + "13 sur 25 (52 %), et celle de la candidature de Sam de 4 % à 32 %.\n", + "\n", + "Ce carnet reproduit cette architecture **à échelle réduite** — 5 agents, une grille\n", + "5×5, deux jours de jeu — avec le **vrai stack local** : le LLM servit par vLLM\n", + "(`swift-1.5-27b`, `enable_thinking` désactivé pour des réponses directes et\n", + "reproductibles) et un **encodeur local** (`paraphrase-multilingual-MiniLM-L12-v2`,\n", + "cache Hugging Face) pour la composante de pertinence. Rien n'est simulé à la main :\n", + "chaque score d'importance, chaque agenda, chaque dialogue et chaque interview vient\n", + "d'un appel réel au moteur.\n", + "\n", + "**Référence** : Park, O'Brien, Cai, Morris, Liang, Bernstein, *Generative Agents:\n", + "Interactive Simulacra of Human Behavior*, UIST 2023 (arXiv 2304.03442). Copie\n", + "archivée dans le gisement partagé : `G:\\Mon Drive\\MyIA\\IA\\Bibliographie IA\\AI\\\n", + "2023 - Park O'Brien Cai - Generative Agents - Interactive Simulacra of Human Behavior\n", + "(arXiv 2304.03442).pdf`. Les chiffres du papier cités ici sont lus depuis cette copie." + ] + }, + { + "cell_type": "markdown", + "id": "6e7a1023-23c8-4f97-ac12-7904be80c481", + "metadata": { + "papermill": { + "duration": 0.003094, + "end_time": "2026-11-04T08:44:04.823496+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:04.820402+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 1. L'architecture en trois briques\n", + "\n", + "Le pipeline de Smallville tient en une boucle et deux mécanismes asynchrones :\n", + "\n", + "1. **Flux de mémoire** (*memory stream*). Chaque observation perçue, chaque dialogue,\n", + " chaque plan devient un enregistrement : description en langage naturel, horodatage\n", + " de création, horodatage de dernier accès, et **score d'importance** (1 à 10) que le\n", + " LLM attribue au moment de la création. Ranger sa chambre vaut 2, inviter quelqu'un\n", + " en rendez-vous vaut 8 — ce sont les exemples du papier.\n", + "2. **Récupération**. Face à une situation, l'agent ne voit pas tout son passé : il\n", + " récupère les *k* souvenirs les mieux notés par\n", + " $$score = \\alpha_{rec}\\cdot recency + \\alpha_{imp}\\cdot importance + \\alpha_{rel}\\cdot relevance$$\n", + " avec, dans le papier, tous les $\\alpha = 1$ après min-max normalisation de chaque\n", + " composante. La fraîcheur est une **décroissance exponentielle de facteur 0,995 par\n", + " heure de jeu** écoulée depuis le dernier accès ; la pertinence est la **similarité\n", + " cosinus** entre l'embedding du souvenir et celui de la requête.\n", + "3. **Réflexion et planification**. Quand la somme des importances récentes dépasse un\n", + " seuil (150 dans le papier, soit ~2-3 réflexions par jour), l'agent se pose des\n", + " questions sur ses 100 derniers enregistrements, récupère les souvenirs qui y\n", + " répondent, et en tire des **insights cités** — qui retournent eux-mêmes dans le flux\n", + " de mémoire : les réflexions peuvent porter sur des réflexions, formant un **arbre**.\n", + " Le plan, lui, décompose la journée en 5 à 8 blocs, puis récursivement en heures,\n", + " puis en tranches de 5 à 15 minutes — et vit aussi dans le flux, donc la récupération\n", + " voit observations, réflexions **et** plans ensemble.\n", + "\n", + "Notre échelle divise tout par cinq : 5 agents, ~15 observations par agent et par jour\n", + "(contre ~100), donc le seuil de réflexion est ramené de 150 à **40** — la cible visée\n", + "reste l'invariant du papier (2 à 3 réflexions par jour), pas son chiffre brut. À cette\n", + "échelle, l'invariant **n'est pas atteint**, mais pas pour la raison qu'on attendrait :\n", + "les cumuls d'importance mesurés **avant** la boucle plafonnent à **9,0**, très en\n", + "dessous du seuil — c'est la simulation elle-même qui fabrique la masse, chaque pas\n", + "ajoutant une observation notée par agent et chaque dialogue deux mémoires. La brique\n", + "**se déclenche donc 5 fois**, une fois par agent, et produit 15 insights (section 5,\n", + "sortie de la cellule de simulation) : une réflexion sur deux jours de jeu là où le\n", + "papier en compte 2 à 3 **quotidiennes** par agent. L'écart est un écart d'échelle, pas\n", + "un défaut de câblage." + ] + }, + { + "cell_type": "markdown", + "id": "91674466-1d59-43ee-b07d-0ef7d3e986a4", + "metadata": { + "papermill": { + "duration": 0.004283, + "end_time": "2026-11-04T08:44:04.832047+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:04.827764+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 2. Câblage du mini-monde\n", + "\n", + "Configuration de l'endpoint vLLM (lecture des variables d'environnement, jamais de\n", + "secret en clair), de l'encodeur local, et définition des briques : grille, lieux,\n", + "agents, enregistrements de mémoire. Les graines de l'expérience de propagation sont\n", + "posées ici : **Isabelle** est la seule à savoir qu'elle organise un vernissage, **Sami**\n", + "le seul à savoir qu'il se présente à l'élection du quartier." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e8004c46-5586-4001-b9e6-08850475ec05", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:44:04.840209Z", + "iopub.status.busy": "2026-11-04T08:44:04.840000Z", + "iopub.status.idle": "2026-11-04T08:44:48.549513Z", + "shell.execute_reply": "2026-11-04T08:44:48.548891Z" + }, + "papermill": { + "duration": 43.718159, + "end_time": "2026-11-04T08:44:48.553556+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:04.835397+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "W1104 09:44:24.818000 38780 site-packages\\torch\\utils\\flop_counter.py:113] triton not found; flop counting will not work for triton kernels\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6cb85530ceac4ac6941c1844ddf4db70", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading weights: 0%| | 0/199 [00:00 contenu texte. Temperature 0 : le carnet doit se rejouer.\n", + " # Le stack vLLM self-hosted connait des stalls transitoires (un appel peut pendre\n", + " # au-dela du timeout alors que le serveur repond aux appels suivants, mesure 04/11) :\n", + " # on reessaie avec pause avant d'abandonner -- meme outil, vraies sorties, plus resilient.\n", + " messages = ([{\"role\": \"system\", \"content\": system}] if system else []) + \\\n", + " [{\"role\": \"user\", \"content\": prompt}]\n", + " payload = {\"model\": VLLM_MODEL, \"messages\": messages,\n", + " \"max_completion_tokens\": max_tokens, \"temperature\": temperature, **_THINKING_OFF}\n", + " derniere_erreur = None\n", + " for essai in range(essais):\n", + " try:\n", + " r = requests.post(f\"{VLLM_BASE_URL}/chat/completions\", headers=HEADERS,\n", + " json=payload, timeout=timeout)\n", + " r.raise_for_status()\n", + " return r.json()[\"choices\"][0][\"message\"].get(\"content\") or \"\"\n", + " except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError) as e:\n", + " derniere_erreur = e\n", + " if essai < essais - 1:\n", + " time.sleep(30)\n", + " print(f\" [retry {essai + 1}] {type(e).__name__}, nouvelle tentative...\")\n", + " raise derniere_erreur\n", + "\n", + "# --- 2) Encodeur local pour la composante relevance (offline, reproductible) ---\n", + "from sentence_transformers import SentenceTransformer\n", + "ENCODER = SentenceTransformer(\"paraphrase-multilingual-MiniLM-L12-v2\", local_files_only=True)\n", + "\n", + "def embed(texte):\n", + " return ENCODER.encode([texte], normalize_embeddings=True)[0]\n", + "\n", + "print(\"vLLM :\", VLLM_BASE_URL, \"| modele :\", VLLM_MODEL)\n", + "print(\"encodeur :\", ENCODER.__class__.__name__, \"| dim :\", len(embed(\"test\")))\n", + "sonde = chat(\"Reponds par un seul mot : pret ?\", max_tokens=8)\n", + "print(\"sonde LLM :\", repr(sonde))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "81e1b9ce-4764-443e-b784-b70a960f05fd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:44:48.562155Z", + "iopub.status.busy": "2026-11-04T08:44:48.561569Z", + "iopub.status.idle": "2026-11-04T08:44:48.570795Z", + "shell.execute_reply": "2026-11-04T08:44:48.569808Z" + }, + "papermill": { + "duration": 0.014287, + "end_time": "2026-11-04T08:44:48.571501+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:48.557214+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "monde initialise : 5 agents, 5 lieux, graine(s) : ['Isabelle', 'Sami']\n" + ] + } + ], + "source": [ + "# --- 3) Les briques du mini-monde ---\n", + "\n", + "HEURES_PAR_PAS = 2.0 # chaque pas de simulation avance le temps de jeu de 2 heures\n", + "DECROISSANCE = 0.995 # facteur de decay par heure de jeu (papier, section 4.1)\n", + "SEUIL_REFLEXION = 40 # papier : 150 a leur cadence ; invariant vise : 2-3 reflexions/jour\n", + "\n", + "LIEUX = { # nom -> case (x, y) de la grille 5x5\n", + " \"Cafe\": (2, 2), \"Bibliotheque\": (1, 3), \"Parc\": (3, 1),\n", + " \"Marche\": (4, 3), \"Atelier\": (0, 0),\n", + "}\n", + "\n", + "@dataclass\n", + "class Memoire:\n", + " description: str\n", + " heure: float # heure de jeu a la creation\n", + " importance: float = 0.0 # 1-10, attribuee par le LLM a la creation\n", + " dernier_acces: float = 0.0 # pour la recency (papier : decay sur le dernier ACCES)\n", + " genre: str = \"observation\" # observation | dialogue | reflexion | plan\n", + " preuves: list = field(default_factory=list) # indices de memoires citees (reflexions)\n", + "\n", + " def cle(self):\n", + " return self.description\n", + "\n", + "@dataclass\n", + "class Agent:\n", + " nom: str\n", + " identite: str # description naturelle : age, metier, traits (papier, section 4.3)\n", + " position: tuple\n", + " memoires: list = field(default_factory=list)\n", + " plan: list = field(default_factory=list) # agenda du jour : (heure, lieu, action)\n", + " dernier_reflexion: float = -1.0\n", + "\n", + " def ajouter(self, monde, description, importance, genre=\"observation\", preuves=None):\n", + " m = Memoire(description, monde[\"heure\"], importance, monde[\"heure\"], genre, preuves or [])\n", + " self.memoires.append(m)\n", + " return m\n", + "\n", + "def nouveau_monde():\n", + " return {\"heure\": 8.0, \"jour\": 1, \"pas\": 0} # le monde commence a 8h du matin\n", + "\n", + "# 5 agents, identites courtes a la maniere du papier (nom, age, occupation, trait)\n", + "AGENTS = {\n", + " \"Isabelle\": Agent(\"Isabelle\", \"Isabelle, 28 ans, peintre. Ouverte et enthousiaste, \"\n", + " \"prepare un vernissage de ses toiles.\", LIEUX[\"Atelier\"]),\n", + " \"Sami\": Agent(\"Sami\", \"Sami, 45 ans, libraire. Reflechi et bavard, tres informe \"\n", + " \"sur la vie du quartier.\", LIEUX[\"Cafe\"]),\n", + " \"Maria\": Agent(\"Maria\", \"Maria, 24 ans, etudiante en chimie. Curieuse, passe ses \"\n", + " \"journees a la bibliotheque.\", LIEUX[\"Bibliotheque\"]),\n", + " \"Klaus\": Agent(\"Klaus\", \"Klaus, 30 ans, chercheur en sociologie urbaine. Metho- \"\n", + " \"dique, observe les habitants du quartier.\", LIEUX[\"Parc\"]),\n", + " \"Awa\": Agent(\"Awa\", \"Awa, 35 ans, boulangere au marche. Sociable, connait tout \"\n", + " \"le monde.\", LIEUX[\"Marche\"]),\n", + "}\n", + "\n", + "MONDE = nouveau_monde()\n", + "\n", + "def nom_lieu(pos):\n", + " for nom, p in LIEUX.items():\n", + " if p == tuple(pos):\n", + " return nom\n", + " return \"ailleurs\"\n", + "\n", + "# Graines de l'experience de propagation (papier section 7.1 : 2 infos, 2 jours).\n", + "# Isabelle seule sait pour le vernissage ; Sami seul sait pour l'election.\n", + "GRAINES = [\n", + " (\"Isabelle\", \"Isabelle organise un vernissage de ses peintures au Cafe le soir du deuxieme jour, et veut y inviter du monde.\"),\n", + " (\"Sami\", \"Sami a decide de se presenter a l'election du quartier et compte en parler autour de lui.\"),\n", + "]\n", + "\n", + "print(\"monde initialise :\", len(AGENTS), \"agents,\", len(LIEUX), \"lieux, graine(s) :\", [g[0] for g in GRAINES])" + ] + }, + { + "cell_type": "markdown", + "id": "2f57467f-1faf-4e17-8c85-261178b6cf06", + "metadata": { + "papermill": { + "duration": 0.00346, + "end_time": "2026-11-04T08:44:48.578552+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:48.575092+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 3. L'importance : le LLM juge chaque souvenir à sa création\n", + "\n", + "Le papier demande au modèle, pour chaque nouvelle mémoire, une note de 1 (purement\n", + "banal) à 10 (extrêmement marquant). À notre cadence nous posons les graines et les\n", + "premières observations de la même façon — le prompt est la traduction fidèle du\n", + "« poignancy prompt » de la section 4.1. Pour tenir le budget d'appels, les notes sont\n", + "demandées **par lot** (une seule requête pour plusieurs descriptions, sortie imposée\n", + "`1: note` par ligne) : c'est un choix d'ingénierie documenté, la sémantique du papier\n", + "(une note par souvenir, au moment de la création) est conservée." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0d68c8e3-024b-4b96-a77c-c71fc6a324ce", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:44:48.587090Z", + "iopub.status.busy": "2026-11-04T08:44:48.586720Z", + "iopub.status.idle": "2026-11-04T08:45:07.771818Z", + "shell.execute_reply": "2026-11-04T08:45:07.771049Z" + }, + "papermill": { + "duration": 19.190501, + "end_time": "2026-11-04T08:45:07.772417+00:00", + "exception": false, + "start_time": "2026-11-04T08:44:48.581916+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 6.0 [Isabelle] Isabelle organise un vernissage de ses peintures au Cafe le soir du de\n", + " 7.0 [Sami] Sami a decide de se presenter a l'election du quartier et compte en pa\n", + " 1.0 [Isabelle] Isabelle commence sa journee ; isabelle, 28 ans, peintre comme a son h\n", + " 1.0 [Sami] Sami commence sa journee ; sami, 45 ans, libraire comme a son habitude\n", + " 1.0 [Maria] Maria commence sa journee ; maria, 24 ans, etudiante en chimie comme a\n", + " 1.0 [Klaus] Klaus commence sa journee ; klaus, 30 ans, chercheur en sociologie urb\n", + " 1.0 [Awa] Awa commence sa journee ; awa, 35 ans, boulangere au marche comme a so\n", + " 1.0 [Isabelle] Isabelle est actuellement au Atelier.\n", + " 1.0 [Sami] Sami est actuellement au Cafe.\n", + " 1.0 [Maria] Maria est actuellement au Bibliotheque.\n", + " 1.0 [Klaus] Klaus est actuellement au Parc.\n", + " 1.0 [Awa] Awa est actuellement au Marche.\n" + ] + } + ], + "source": [ + "def noter_par_lot(descriptions):\n", + " # Une requete pour plusieurs souvenirs ; sortie imposee \"i: note\" (i = position, 1-based).\n", + " lignes = \"\\n\".join(f\"{i}. {d}\" for i, d in enumerate(descriptions, 1))\n", + " reponse = chat(\n", + " \"Sur une echelle de 1 a 10, ou 1 est purement banal (ex. ranger sa chambre, se \"\n", + " \"brosser les dents) et 10 extremement marquant (ex. une rupture, une admission), \"\n", + " \"note la saillance de chaque souvenir ci-dessous. Reponds EXACTEMENT une ligne \"\n", + " \"par souvenir au format 'numero: note entiere', sans autre texte.\\n\\nSouvenirs:\\n\"\n", + " + lignes, max_tokens=200)\n", + " notes = {}\n", + " for ligne in reponse.strip().splitlines():\n", + " m = re.match(r\"\\s*(\\d+)\\s*[:.]\\s*(\\d+)\", ligne)\n", + " if m:\n", + " i, note = int(m.group(1)), int(m.group(2))\n", + " if 1 <= i <= len(descriptions):\n", + " notes[i] = float(min(10, max(1, note)))\n", + " return [notes.get(i, 5.0) for i in range(1, len(descriptions) + 1)] # 5 = repli neutre\n", + "\n", + "# Les graines + 2 observations initiales par agent : tout est note par le LLM.\n", + "a_poser = []\n", + "for nom, texte in GRAINES:\n", + " a_poser.append((nom, texte))\n", + "for nom, a in AGENTS.items():\n", + " a_poser.append((nom, f\"{nom} commence sa journee ; {a.identite.split('.')[0].lower()} comme a son habitude.\"))\n", + "for nom, a in AGENTS.items():\n", + " a_poser.append((nom, f\"{nom} est actuellement au {nom_lieu(a.position)}.\"))\n", + "\n", + "notes = noter_par_lot([t for _, t in a_poser])\n", + "for (nom, texte), note in zip(a_poser, notes):\n", + " AGENTS[nom].ajouter(MONDE, texte, note)\n", + " print(f\"{note:4.1f} [{nom}] {texte[:70]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9dc2665f-8bba-41a4-b42d-f2fa128419eb", + "metadata": { + "papermill": { + "duration": 0.003255, + "end_time": "2026-11-04T08:45:07.779431+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.776176+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "Les graines porteuses d'information (vernissage, élection) doivent sortir nettement\n", + "plus haut que les banalités de début de journée — c'est le premier contrôle que le\n", + "score d'importance fait ce qu'on attend de lui : distinguer le mundane du poignant." + ] + }, + { + "cell_type": "markdown", + "id": "f14867e7-c243-46ac-a216-96ed05a71269", + "metadata": { + "papermill": { + "duration": 0.003035, + "end_time": "2026-11-04T08:45:07.785531+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.782496+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 4. La récupération : trois scores, min-max, somme pondérée\n", + "\n", + "Chaque composante est ramenée sur $[0, 1]$ avant sommation — pour la fraîcheur la\n", + "formule du papier est déjà bornée ($0{,}995^{\\Delta h}$), l'importance se divise par\n", + "10, la pertinence cosinus est bornée par construction. Le papier applique en plus un\n", + "min-max **par lot candidat** ; sur nos petits lots l'effet et la division directe\n", + "coïncident, nous gardons la forme bornée par lot pour la fidélité." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "621453c6-e870-4c95-9569-d8a98169831b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:45:07.793418Z", + "iopub.status.busy": "2026-11-04T08:45:07.793212Z", + "iopub.status.idle": "2026-11-04T08:45:07.865189Z", + "shell.execute_reply": "2026-11-04T08:45:07.864317Z" + }, + "papermill": { + "duration": 0.07695, + "end_time": "2026-11-04T08:45:07.865810+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.788860+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requete : une soiree se prepare-t-elle ? (memoires de Maria)\n", + " rec imp rel total description\n", + " 0.50 0.50 1.00 2.00 [observation] Maria commence sa journee ; maria, 24 ans, etudiante en chim\n", + " 0.50 0.50 0.00 1.00 [observation] Maria est actuellement au Bibliotheque.\n" + ] + } + ], + "source": [ + "def normaliser(valeurs):\n", + " # min-max par lot (papier : \"normalize ... to [0,1] using min-max scaling\").\n", + " lo, hi = min(valeurs), max(valeurs)\n", + " if hi - lo < 1e-12:\n", + " return [0.5] * len(valeurs)\n", + " return [(v - lo) / (hi - lo) for v in valeurs]\n", + "\n", + "def recuperer(agent, requete, monde, k=3, a_rec=1.0, a_imp=1.0, a_rel=1.0):\n", + " # score = a_rec*recency + a_imp*importance + a_rel*relevance (papier : tous les alpha = 1)\n", + " req = embed(requete)\n", + " pool = agent.memoires\n", + " rec = [DECROISSANCE ** (monde[\"heure\"] - m.dernier_acces) for m in pool]\n", + " imp = [m.importance / 10.0 for m in pool]\n", + " rel = [float(np.dot(embed(m.description), req)) for m in pool] # cosinus ( vecteurs normalises )\n", + " rec_n, imp_n, rel_n = normaliser(rec), normaliser(imp), normaliser(rel)\n", + " scores = [a_rec * r + a_imp * i + a_rel * l for r, i, l in zip(rec_n, imp_n, rel_n)]\n", + " rang = sorted(range(len(pool)), key=lambda j: scores[j], reverse=True)[:k]\n", + " for j in rang:\n", + " pool[j].dernier_acces = monde[\"heure\"] # la recency decroit depuis le dernier ACCES\n", + " return [(pool[j], rec_n[j], imp_n[j], rel_n[j], scores[j]) for j in rang]\n", + "\n", + "# Demonstration : que remonte la requete \"soiree\" chez Maria, qui ne sait rien encore ?\n", + "demo = recuperer(AGENTS[\"Maria\"], \"Est-ce qu'une soiree se prepare dans le quartier ?\", MONDE, k=3)\n", + "print(\"Requete : une soiree se prepare-t-elle ? (memoires de Maria)\")\n", + "print(f\"{'rec':>5} {'imp':>5} {'rel':>5} {'total':>6} description\")\n", + "for m, r, i, l, s in demo:\n", + " print(f\"{r:5.2f} {i:5.2f} {l:5.2f} {s:6.2f} [{m.genre}] {m.description[:60]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fed1c90a-3869-4070-a170-300c70c1e8ba", + "metadata": { + "papermill": { + "duration": 0.003414, + "end_time": "2026-11-04T08:45:07.873019+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.869605+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 5. La réflexion : dépasser l'observation brute\n", + "\n", + "Quand la somme des importances des mémoires créées depuis la dernière réflexion\n", + "dépasse le seuil, l'agent s'arrête et généralise : trois questions salientes sur ses\n", + "derniers enregistrements, récupération pour chaque question, puis des insights qui\n", + "**citent leurs preuves**. Les réflexions retournent dans le flux avec un `importance`\n", + "haute (le papier les traite comme des mémoires à part entière) et leurs pointeurs\n", + "forment l'arbre de réflexion. À notre échelle : **une** question au lieu de trois, et\n", + "importance fixée à 8 — la mécanique citée (insight + pointeurs de preuves) est intacte,\n", + "le budget d'appels divisé.\n", + "\n", + "**Ce que ce run montre, et ce qu'il ne montre pas.** La mécanique est câblée —\n", + "`peut_etre_reflechir` est appelée à chaque pas — et **elle se déclenche** : les cinq\n", + "agents ont chacun franchi le seuil de 40 une fois en deux jours de jeu, produisant\n", + "**5 réflexions et 15 insights** (Isabelle et Sami à j1 20h, Klaus et Awa à j2 14h,\n", + "Maria à j2 20h — trois insights chacun, affichés par la cellule de simulation). Le\n", + "piège de lecture à éviter : les cumuls mesurés **avant** la boucle plafonnent à 9,0\n", + "et n'auraient jamais déclenché la brique — c'est la boucle elle-même qui crée la\n", + "masse (observations, dialogues notés), et le compteur repart de zéro après chaque\n", + "réflexion. Le papier, à ~100 observations par agent et par jour contre ~15 ici,\n", + "observe 2 à 3 réflexions **quotidiennes** par agent : notre cadence reste ~4 fois\n", + "plus lente — fait d'échelle, pas de câblage — et les deux leviers pour la rapprocher\n", + "sont de baisser le seuil (exercice 2) ou d'allonger la simulation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "eefca6c5-9856-4126-a384-d861c6c33a79", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:45:07.881470Z", + "iopub.status.busy": "2026-11-04T08:45:07.881187Z", + "iopub.status.idle": "2026-11-04T08:45:07.888088Z", + "shell.execute_reply": "2026-11-04T08:45:07.887523Z" + }, + "papermill": { + "duration": 0.012363, + "end_time": "2026-11-04T08:45:07.888880+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.876517+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Seuil de reflexion actif : 40 | cumuls d'importance avant la boucle :\n", + " Isabelle cumul = 8.0\n", + " Sami cumul = 9.0\n", + " Maria cumul = 2.0\n", + " Klaus cumul = 2.0\n", + " Awa cumul = 2.0\n" + ] + } + ], + "source": [ + "NB_APPELS = [0] # compteur d'appels LLM, pour lire le budget du carnet en sortie\n", + "\n", + "_chat_brut = chat\n", + "def chat(*a, **kw):\n", + " NB_APPELS[0] += 1\n", + " return _chat_brut(*a, **kw)\n", + "\n", + "def peut_etre_reflechir(agent, monde, seuil=SEUIL_REFLEXION):\n", + " # Invariant vise (papier) : 2-3 reflexions par jour. A leur cadence (~100 obs/jour)\n", + " # le seuil brut est 150 ; a la notre (~15/jour) on le ramene a 40. Autre baisse\n", + " # d'echelle assumee et mesuree : UNE question saliente au lieu de trois, et\n", + " # l'importance des reflexions fixee a 8 (haute) plutost que notee par lot --\n", + " # la mecanique citee du papier (insights + pointeurs de preuves) est intacte.\n", + " cumul = sum(m.importance for m in agent.memoires\n", + " if m.heure > agent.dernier_reflexion)\n", + " if cumul < seuil or len(agent.memoires) < 6:\n", + " return None\n", + " agent.dernier_reflexion = monde[\"heure\"]\n", + " dernieres = agent.memoires[-20:] # papier : 100 derniers ; notre echelle : 20\n", + " lignes = \"\\n\".join(f\"{i}. {m.description}\" for i, m in enumerate(dernieres, 1))\n", + " question = chat(\"Voici les derniers evenements de la memoire de \" + agent.nom +\n", + " \".\\n\" + lignes +\n", + " \"\\n\\nQuelle est LA question de plus haut niveau que ces evenements \"\n", + " \"permettent de poser ? Une seule question, une seule ligne.\",\n", + " max_tokens=60).strip().splitlines()[0]\n", + " preuves_pool = recuperer(agent, question, monde, k=6)\n", + " corps = \"\\n\".join(f\"{i}. {m.description}\" for i, (m, *_) in enumerate(preuves_pool, 1))\n", + " sortie = chat(\"Enonces sur \" + agent.nom + \" :\\n\" + corps +\n", + " \"\\n\\nQuelles sont les 3 observations de haut niveau qu'on peut en \"\n", + " \"inferer ? Format exact : une par ligne, 'insight (parce que 1, 3)' \"\n", + " \"-- les numeros citent les enonces preuves.\", max_tokens=200)\n", + " arbre = []\n", + " for ligne in sortie.strip().splitlines():\n", + " m = re.match(r\"\\s*(.+?)\\s*\\(parce que ([0-9, ]+)\\)\\s*$\", ligne)\n", + " if m:\n", + " idx = [int(x) - 1 for x in re.findall(r\"\\d+\", m.group(2))\n", + " if 0 < int(x) <= len(preuves_pool)]\n", + " arbre.append(agent.ajouter(monde, agent.nom + \" realise : \" + m.group(1),\n", + " 8.0, genre=\"reflexion\", preuves=idx))\n", + " return arbre\n", + "\n", + "# Mesure prise ICI, avant la boucle : la somme porte sur toutes les memoires\n", + "# presentes a cet instant. Pendant la simulation, le seuil se juge sur les seules\n", + "# memoires creees depuis la derniere reflexion -- le cumul repart de zero a chaque\n", + "# declenchement, donc les deux nombres ne mesurent pas la meme quantite.\n", + "print(\"Seuil de reflexion actif :\", SEUIL_REFLEXION,\n", + " \"| cumuls d'importance avant la boucle :\")\n", + "for a in AGENTS.values():\n", + " print(f\" {a.nom:9s} cumul = {sum(m.importance for m in a.memoires):5.1f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "e0ab31c5-6431-4755-b92e-7c696e0fa3ea", + "metadata": { + "papermill": { + "duration": 0.003837, + "end_time": "2026-11-04T08:45:07.896478+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.892641+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 6. La planification : un agenda en 5 à 8 blocs\n", + "\n", + "Le plan de journée se génère depuis l'identité de l'agent et le résumé de sa veille\n", + "(nous passons ses graines et premières mémoires), puis vit dans le flux comme les\n", + "autres mémoires — la récupération peut donc le rappeler. Le papier décompose ensuite\n", + "récursivement (heures, puis 5-15 minutes) ; à notre pas de temps de 2 heures, la\n", + "décomposition en blocs horaires **est** la granularité de simulation, nous nous\n", + "arrêtons là et le notons." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f7ad2242-5ff1-452c-bd1e-3310bf6a62a3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:45:07.905021Z", + "iopub.status.busy": "2026-11-04T08:45:07.904797Z", + "iopub.status.idle": "2026-11-04T08:45:44.076938Z", + "shell.execute_reply": "2026-11-04T08:45:44.076208Z" + }, + "papermill": { + "duration": 36.180545, + "end_time": "2026-11-04T08:45:44.080824+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:07.900279+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 8h-10h Atelier Peinture de nouvelles toiles\n", + " 10h-12h Atelier Séchage et préparation des cadres\n", + " 12h-14h Cafe Déjeuner et pause\n", + " 14h-17h Atelier Finitions et étiquetage des œuvres\n", + " 17h-19h Cafe Installation de l'espace pour le vernissage\n" + ] + } + ], + "source": [ + "def planifier(agent, monde):\n", + " resume = \" ; \".join(m.description for m in agent.memoires[:8])\n", + " sortie = chat(\n", + " f\"Nom : {agent.identite}\\nDernier evenements connus : {resume}\\n\"\n", + " f\"Nous sommes au jour {monde['jour']}, il est {monde['heure']:.0f}h. Les lieux \"\n", + " f\"possibles : {', '.join(LIEUX)}.\\n\\nEcris l'agenda de {agent.nom} pour le reste \"\n", + " \"du jour : 5 a 6 lignes, une par bloc, au format strict \"\n", + " \"'HHh-HHh | Lieu | action courante'. Rien d'autre.\", max_tokens=220)\n", + " agent.plan = []\n", + " for ligne in sortie.strip().splitlines():\n", + " m = re.match(r\"\\s*(\\d{1,2})h\\s*-\\s*(\\d{1,2})h\\s*\\|\\s*([A-Za-z]+)\\s*\\|\\s*(.+)\", ligne)\n", + " if m:\n", + " debut, fin, lieu, action = int(m.group(1)), int(m.group(2)), m.group(3), m.group(4).strip()\n", + " if lieu in LIEUX:\n", + " agent.plan.append((debut, fin, lieu, action))\n", + " agent.plan.sort()\n", + " if agent.plan:\n", + " texte = f\"{agent.nom} prevu : \" + \" ; \".join(f\"{d}h-{f}h {l} ({a})\" for d, f, l, a in agent.plan)\n", + " agent.ajouter(monde, texte, 5.0, genre=\"plan\") # importance fixe (documente)\n", + " return agent.plan\n", + "\n", + "PLAN_DEMO = planifier(AGENTS[\"Isabelle\"], MONDE)\n", + "for bloc in PLAN_DEMO:\n", + " print(f\" {bloc[0]:2d}h-{bloc[1]:2d}h {bloc[2]:12s} {bloc[3][:50]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f1f01e6a-aae4-4a17-9166-62e1d6d9fc43", + "metadata": { + "papermill": { + "duration": 0.003413, + "end_time": "2026-11-04T08:45:44.087690+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:44.084277+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 7. La boucle d'action : percevoir, suivre le plan, parler\n", + "\n", + "À chaque pas : chaque agent perçoit son lieu et ses voisins (mémoire notée), suit son\n", + "agenda (déplacement déterministe vers le bloc courant — à notre échelle le\n", + "« continuer ou réagir » du papier se joue au moment de la rencontre), et quand deux\n", + "agents partagent une case, **un dialogue réel** est généré : chacun reçoit le point de\n", + "vue de l'autre filtré par sa propre récupération. Le dialogue est le **vecteur de\n", + "propagation** de l'information — c'est lui que les compteurs suivent." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1adfa0f6-7091-40ac-8217-6d84f6a3ef25", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T08:45:44.096083Z", + "iopub.status.busy": "2026-11-04T08:45:44.095881Z", + "iopub.status.idle": "2026-11-04T09:20:16.840731Z", + "shell.execute_reply": "2026-11-04T09:20:16.839754Z" + }, + "papermill": { + "duration": 2072.753794, + "end_time": "2026-11-04T09:20:16.844959+00:00", + "exception": false, + "start_time": "2026-11-04T08:45:44.091165+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 reflexion(s) declenchee(s), 15 insight(s) :\n", + " j1 20h Isabelle -- 3 insight(s)\n", + " Isabelle realise : Isabelle est une artiste qui organise un vernissage\n", + " Isabelle realise : Isabelle a une relation amicale avec Sami\n", + " Isabelle realise : Sami est un candidat aux élections du quartier\n", + " j1 20h Sami -- 3 insight(s)\n", + " Sami realise : Sami a l'intention de se présenter aux élections du quartier\n", + " Sami realise : Sami a discuté avec Isabelle\n", + " Sami realise : Sami est au café\n", + " j2 14h Klaus -- 3 insight(s)\n", + " Klaus realise : Klaus adopte une posture d'observateur systématique et fasciné par l'organisation spontanée des dynamiques sociales locales\n", + " Klaus realise : Klaus structure rigoureusement sa journée de recherche en alternant entre observation de terrain et interactions ciblées avec les habitants\n", + " Klaus realise : Klaus entretient des échanges récurrents avec les mêmes interlocuteurs (Awa et Maria) tout en adaptant son discours à leurs contextes respectifs\n", + " j2 14h Awa -- 3 insight(s)\n", + " Awa realise : Awa est le pivot social central du quartier, servant de nœud de connexion entre les différents acteurs\n", + " Awa realise : Awa entretient des relations interpersonnelles distinctes et personnalisées avec chaque voisin, adaptant son ton selon le contexte\n", + " Awa realise : La routine quotidienne d'Awa est structurée autour de son activité marchande, qui détermine ses déplacements et ses interactions sociales\n", + " j2 20h Maria -- 3 insight(s)\n", + " Maria realise : Maria est profondément absorbée par ses études de chimie organique\n", + " Maria realise : Maria utilise la bibliothèque comme lieu principal de travail et de concentration\n", + " Maria realise : Maria entretient des interactions sociales variées avec des personnes aux profils différents\n", + "\n", + "16 dialogue(s) generes sur 14 pas (jour 3)\n", + " j1 12h Isabelle<->Sami : Isabelle: Salut Sami ! Je suis aux petits soins pour mon vernissage ce soir, j'ai hâte de voir tout le monde a\n", + " j1 18h Isabelle<->Sami : Isabelle: Salut Sami ! Je suis aux petits soins pour mon vernissage ce soir, j'ai hâte de voir tout le monde a\n", + " j1 20h Isabelle<->Sami : Isabelle: Salut Sami ! Je suis aux petits soins pour mon vernissage ce soir, j'ai hâte de voir tout le monde a\n", + " j2 10h Klaus<->Maria : Klaus: J'observe les dynamiques sociales ici, c'est fascinant de voir comment les gens s'organisent spontanéme\n", + " j2 10h Awa<->Maria : Awa: Bonjour Maria, tu as l'air bien fatiguée, tu as passé toute la matinée à la bibliothèque ? Maria: Oui, je\n", + " j2 10h Awa<->Klaus : Awa: Bonjour Klaus, vous êtes bien tôt pour un chercheur, le café est à peine chaud ! Klaus: Bonjour Awa, je s\n", + " j2 12h Klaus<->Sami : Klaus: Bonjour Sami, je suis en pleine analyse des flux de circulation au marché, c'est fascinant de voir l'or\n", + " j2 12h Awa<->Sami : Awa: Salut Sami, je vois que tu es là avec Maria et Klaus, c’est le petit monde du quartier qui se retrouve ! \n", + " j2 12h Awa<->Klaus : Awa: Klaus, vous êtes déjà là ? J’ai couru toute la matinée au marché, je connais tout le monde, c’est ma vie \n", + " j2 14h Klaus<->Maria : Klaus: Je note avec fascination l'organisation spontanée des habitants que j'observe ici. Maria: C'est captiva\n" + ] + } + ], + "source": [ + "def bloc_courant(agent, heure):\n", + " for d, f, lieu, action in agent.plan:\n", + " if d <= heure < f:\n", + " return lieu, action\n", + " return None, None\n", + "\n", + "REFLEXIONS = [] # (jour, heure, nom, [memoires d'insight]) -- une entree par declenchement\n", + "\n", + "def avancer(monde, pas_max=None):\n", + " journal = []\n", + " while True:\n", + " if pas_max is not None and monde[\"pas\"] >= pas_max:\n", + " return journal\n", + " heure = monde[\"heure\"]\n", + " if heure >= 22.0: # fin de journee : le jour tourne\n", + " monde[\"jour\"] += 1\n", + " monde[\"heure\"] = 8.0\n", + " if monde[\"jour\"] > 2: # 2 jours de jeu, comme le papier\n", + " return journal\n", + " for a in AGENTS.values():\n", + " planifier(a, monde) # nouveau plan chaque matin\n", + " continue\n", + " # 1) deplacement selon le plan + perception\n", + " nouvelles = []\n", + " for a in AGENTS.values():\n", + " lieu, action = bloc_courant(a, heure)\n", + " if lieu:\n", + " a.position = LIEUX[lieu]\n", + " visibles = [b.nom for b in AGENTS.values() if b is not a and b.position == a.position]\n", + " nouvelles.append((a, f\"{a.nom} est au {nom_lieu(a.position)}\" +\n", + " (f\" et apercoit {', '.join(visibles)}.\" if visibles else \".\")))\n", + " # 2) dialogues : une rencontre par pas et par paire\n", + " vus = set()\n", + " for a in AGENTS.values():\n", + " for b in AGENTS.values():\n", + " if a.nom >= b.nom or (a.nom, b.nom) in vus:\n", + " continue\n", + " if a.position != b.position:\n", + " continue\n", + " vus.add((a.nom, b.nom))\n", + " extrait_a = \" ; \".join(m.description for m, *_ in recuperer(a, f\"de quoi parler avec {b.nom}\", monde, k=3))\n", + " extrait_b = \" ; \".join(m.description for m, *_ in recuperer(b, f\"de quoi parler avec {a.nom}\", monde, k=3))\n", + " echange = chat(\n", + " f\"{a.identite} et {b.nom} se croisent au {nom_lieu(a.position)}.\\n\"\n", + " f\"Ce que {a.nom} a en tete : {extrait_a}\\n\"\n", + " f\"Ce que {b.nom} a en tete : {extrait_b}\\n\\n\"\n", + " f\"Ecris un tres court echange (2 repliques chacune, une ligne par \"\n", + " f\"replique au format '{a.nom}: ...' / '{b.nom}: ...') ou chacun parle \"\n", + " f\"naturellement de ce qui lui tient a coeur. Rien d'autre.\", max_tokens=260)\n", + " nouvelles.append((a, f\"{a.nom} a discute avec {b.nom} : {echange.replace(chr(10), ' ')}\"))\n", + " nouvelles.append((b, f\"{b.nom} a discute avec {a.nom} : {echange.replace(chr(10), ' ')}\"))\n", + " journal.append((monde['jour'], heure, a.nom, b.nom, echange.replace(chr(10), ' ')[:110]))\n", + " # 3) notation en lot des nouvelles memories + reflexion eventuelle\n", + " if nouvelles:\n", + " notes = noter_par_lot([t for _, t in nouvelles])\n", + " for (a, t), n in zip(nouvelles, notes):\n", + " a.ajouter(monde, t, n)\n", + " for a in AGENTS.values():\n", + " arbres = peut_etre_reflechir(a, monde)\n", + " if arbres is not None: # None = seuil non atteint a ce pas\n", + " REFLEXIONS.append((monde[\"jour\"], heure, a.nom, arbres))\n", + " monde[\"pas\"] += 1\n", + " monde[\"heure\"] += HEURES_PAR_PAS\n", + "\n", + "JOURNAL = avancer(MONDE)\n", + "n_insights = sum(len(r[3]) for r in REFLEXIONS)\n", + "if REFLEXIONS:\n", + " print(f\"{len(REFLEXIONS)} reflexion(s) declenchee(s), {n_insights} insight(s) :\")\n", + " for jour, h, nom, arbres in REFLEXIONS:\n", + " print(f\" j{jour} {h:4.0f}h {nom} -- {len(arbres)} insight(s)\")\n", + " for m in arbres:\n", + " print(f\" {m.description}\")\n", + "else:\n", + " print(\"0 reflexion declenchee : aucune somme cumulee n'a atteint le seuil.\")\n", + "print()\n", + "print(f\"{len(JOURNAL)} dialogue(s) generes sur {MONDE['pas']} pas (jour {MONDE['jour']})\")\n", + "for j in JOURNAL[:10]:\n", + " print(f\" j{j[0]} {j[1]:4.0f}h {j[2]}<->{j[3]} : {j[4]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "d74457f5-ccb9-450c-ac85-843c5d1a3138", + "metadata": { + "papermill": { + "duration": 0.00404, + "end_time": "2026-11-04T09:20:16.853517+00:00", + "exception": false, + "start_time": "2026-11-04T09:20:16.849477+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 8. L'expérience de propagation : compteurs et interviews\n", + "\n", + "Comme le papier (section 7.1.1) : deux graines d'information, deux jours de jeu, puis\n", + "**interview** de chaque agent (« Sais-tu pour le vernissage ? pour l'élection ? ») —\n", + "et vérification anti-hallucination : une réponse « oui » ne compte que si le flux de\n", + "mémoire contient effectivement l'information (le papier fait cette vérification pour\n", + "chaque réponse positive).\n", + "\n", + "Un détail de lecture avant d'interpréter les compteurs : la dernière ligne de la sortie\n", + "de la section 7 affiche `(jour 3)` alors que les dialogues sont horodatés `j1` et `j2`.\n", + "Le compteur est incrémenté avant le test de sortie de boucle — les deux jours de jeu\n", + "sont bien j1 et j2, et `17 dialogue(s) generes sur 14 pas` le confirme (14 pas × 2 h).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4167d6da-c074-475f-816e-4e43b107330d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T09:20:16.863180Z", + "iopub.status.busy": "2026-11-04T09:20:16.862859Z", + "iopub.status.idle": "2026-11-04T09:25:09.822846Z", + "shell.execute_reply": "2026-11-04T09:25:09.822086Z" + }, + "papermill": { + "duration": 292.970129, + "end_time": "2026-11-04T09:25:09.827601+00:00", + "exception": false, + "start_time": "2026-11-04T09:20:16.857472+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "agent | dit vern. | mem. vern. | dit elect. | mem. elect.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Isabelle | True | True | False | False \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sami | True | True | False | True \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Maria | False | False | False | False \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Klaus | False | True | True | False \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Awa | False | False | True | False \n", + "\n", + "Propagation : vernissage 2/5 (40%) | election 0/5 (0%)\n", + "Papier (25 agents, 2 jours) : fete 4% -> 52% | candidature 4% -> 32%\n", + "Budget : 51 appels LLM au total pour ce mini-monde\n" + ] + } + ], + "source": [ + "JETONS = {\"vernissage\": \"vernissage\", \"election\": \"election\"} # cles : infos suiveies\n", + "\n", + "def connait_en_memoire(agent, cle):\n", + " return any(cle in m.description.lower() for m in agent.memoires)\n", + "\n", + "def interviewer(agent):\n", + " reponse = chat(\n", + " f\"Tu es {agent.identite} Reponds a la question comme cette personne, en une \"\n", + " \"phrase par question, format strict 'vernissage: oui/non parce que ...' puis \"\n", + " \"'election: oui/non parce que ...'.\\n\"\n", + " \"Q1 : Sais-tu qu'un vernissage se prepare au cafe ?\\n\"\n", + " \"Q2 : Sais-tu qui se presente a l'election du quartier ?\", max_tokens=140)\n", + " dit = {}\n", + " for cle in JETONS:\n", + " m = re.search(cle + r\"\\s*:\\s*(oui|non)\", reponse, re.IGNORECASE)\n", + " dit[cle] = bool(m and m.group(1).lower() == \"oui\")\n", + " return dit, reponse\n", + "\n", + "print(f\"{'agent':10s} | {'dit vern.':9s} | {'mem. vern.':9s} | {'dit elect.':9s} | {'mem. elect.':9s}\")\n", + "n_vern = n_elect = 0\n", + "for a in AGENTS.values():\n", + " dit, brut = interviewer(a)\n", + " m_vern, m_elect = connait_en_memoire(a, \"vernissage\"), connait_en_memoire(a, \"election\")\n", + " # Anti-hallucination (papier 7.1.1) : un 'oui' ne compte que si la memoire le porte.\n", + " ok_vern = dit[\"vernissage\"] and m_vern\n", + " ok_elect = dit[\"election\"] and m_elect\n", + " n_vern += ok_vern; n_elect += ok_elect\n", + " print(f\"{a.nom:10s} | {str(dit['vernissage']):9s} | {str(m_vern):9s} | {str(dit['election']):9s} | {str(m_elect):9s}\")\n", + "N = len(AGENTS)\n", + "print(f\"\\nPropagation : vernissage {n_vern}/{N} ({100*n_vern/N:.0f}%) | election {n_elect}/{N} ({100*n_elect/N:.0f}%)\")\n", + "print(\"Papier (25 agents, 2 jours) : fete 4% -> 52% | candidature 4% -> 32%\")\n", + "print(f\"Budget : {NB_APPELS[0]} appels LLM au total pour ce mini-monde\")" + ] + }, + { + "cell_type": "markdown", + "id": "7ecc8c72-79d8-4355-9cb9-46d1696e3a08", + "metadata": { + "papermill": { + "duration": 0.00387, + "end_time": "2026-11-04T09:25:09.835709+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.831839+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "À lire côte à côte avec le papier : la **dynamique** (une graine passe de 1 agent à\n", + "une fraction du monde en deux jours, portée par les dialogues) se reproduit à 5\n", + "agents ; les **taux** ne se comparent pas directement — la probabilité de rencontre\n", + "dépends de la taille du monde, de la longueur des dialogues et du nombre de pas. Le\n", + "papier rapporte aussi que l'ablation de chaque brique coûte cher : en évaluation par\n", + "interviews notées TrueSkill, l'architecture complète marque $\\mu = 29{,}89$, sans la\n", + "réflexion $26{,}88$, sans réflexion ni planification $25{,}64$, un crowdworker humain\n", + "$22{,}95$, et la baseline sans mémoire $21{,}21$ — l'écart complet/baseline vaut\n", + "$d = 8{,}16$ écarts-types. Les réflexions et les plans ne sont pas du décor : ce sont\n", + "eux qui font passer Klaus du voisin le plus croisé à la personne la plus pertinente." + ] + }, + { + "cell_type": "markdown", + "id": "e5ea3d2e-6e82-4d64-8438-42b7d3824df5", + "metadata": { + "papermill": { + "duration": 0.004046, + "end_time": "2026-11-04T09:25:09.843682+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.839636+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 9. Exercices\n", + "\n", + "Trois exercices, à compléter sans jamais lever d'erreur (le carnet doit rester\n", + "exécutable de bout en bout, règles C.1) : chaque stub retourne `None`, le TODO et les\n", + "indices restent en place." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d8ead50f-b74d-409e-94d0-465091da1f07", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T09:25:09.853132Z", + "iopub.status.busy": "2026-11-04T09:25:09.852934Z", + "iopub.status.idle": "2026-11-04T09:25:09.857182Z", + "shell.execute_reply": "2026-11-04T09:25:09.856508Z" + }, + "papermill": { + "duration": 0.009785, + "end_time": "2026-11-04T09:25:09.857806+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.848021+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer -- balayage : None\n" + ] + } + ], + "source": [ + "# Exercice 1 -- Balayer les poids de recuperation.\n", + "# Le papier fixe tous les alpha a 1. La question : que perd-on en privilegiant une\n", + "# composante ? Implemente `balayage` : pour a_rel dans [0, 0.5, 1, 2], recupere le\n", + "# top-3 chez Isabelle pour la requete \"qui organise le vernissage ?\" et retourne la\n", + "# table (a_rel, rang du souvenir-graine). Retourne None tant que ce n'est pas fait.\n", + "# Indice : recuperer(agent, requete, monde, k=3, a_rel=...) accepte les trois poids.\n", + "# Etape 1 : boucle sur les valeurs. Etape 2 : reperer la graine par 'vernissage' dans\n", + "# la description. Etape 3 : imprimer le rang observe pour chaque poids.\n", + "\n", + "def balayage(agent, requete, monde):\n", + " # TODO etudiant\n", + " return None\n", + "\n", + "print(\"Exercice a completer -- balayage :\", balayage(AGENTS[\"Isabelle\"], \"qui organise le vernissage ?\", MONDE))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f563a3af-b684-44a5-b8af-59448a57e7c6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T09:25:09.866997Z", + "iopub.status.busy": "2026-11-04T09:25:09.866698Z", + "iopub.status.idle": "2026-11-04T09:25:09.870783Z", + "shell.execute_reply": "2026-11-04T09:25:09.870042Z" + }, + "papermill": { + "duration": 0.009468, + "end_time": "2026-11-04T09:25:09.871565+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.862097+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer -- profondeur2 : None\n" + ] + } + ], + "source": [ + "# Exercice 2 -- Arbre de reflexion de profondeur 2.\n", + "# Les reflexions citees par une reflexion forment un arbre. La cellule 5 stocke les\n", + "# preuves : implemente `profondeur2` qui, pour un agent donne, retourne la liste des\n", + "# reflexions qui citent au moins une autre reflexion (les noeuds internes de l'arbre).\n", + "# Indice : m.genre == 'reflexion' filtre le bon niveau ; les preuves vivent dans\n", + "# m.preuves (indices). Retourne None tant que ce n'est pas fait.\n", + "\n", + "def profondeur2(agent):\n", + " # TODO etudiant\n", + " return None\n", + "\n", + "print(\"Exercice a completer -- profondeur2 :\", profondeur2(AGENTS[\"Isabelle\"]))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8cac04cd-47c3-4131-8bc5-1bd16ebdf315", + "metadata": { + "execution": { + "iopub.execute_input": "2026-11-04T09:25:09.881375Z", + "iopub.status.busy": "2026-11-04T09:25:09.881061Z", + "iopub.status.idle": "2026-11-04T09:25:09.884573Z", + "shell.execute_reply": "2026-11-04T09:25:09.884053Z" + }, + "papermill": { + "duration": 0.00941, + "end_time": "2026-11-04T09:25:09.885228+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.875818+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer -- prediction : None\n" + ] + } + ], + "source": [ + "# Exercice 3 -- Une troisieme graine.\n", + "# Ajoute un sixieme agent (choisis identite et lieu) porteur d'une troisieme\n", + "# information (par exemple une disparition mysterieuse au marche), puis predit AVANT\n", + "# de relancer `avancer` : combien d'agents la connaitront apres deux jours, et pourquoi\n", + "# (quels parametres de la boucle portent la propagation ?). Retourne None tant que la\n", + "# prediction n'est pas ecrite dans la variable `prediction` (une chaine courte).\n", + "\n", + "prediction = None # TODO etudiant : ecris ta prediction ici (chaine courte)\n", + "\n", + "def sixieme_agent():\n", + " # TODO etudiant : construire l'Agent, l'ajouter a AGENTS, poser la graine notee.\n", + " return None\n", + "\n", + "print(\"Exercice a completer -- prediction :\", prediction)" + ] + }, + { + "cell_type": "markdown", + "id": "326514e1-09f7-423d-9e3c-419aee2b4dd6", + "metadata": { + "papermill": { + "duration": 0.003698, + "end_time": "2026-11-04T09:25:09.892836+00:00", + "exception": false, + "start_time": "2026-11-04T09:25:09.889138+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 10. Ce qu'il faut retenir\n", + "\n", + "- La mémoire d'un agent génératif n'est pas un contexte qui gonfle : c'est un **flux**\n", + " noté, dont une **récupération à trois composantes** tire, à chaque situation, le\n", + " passé qui compte — fraîcheur (decay 0,995/h), importance (le LLM juge à la création),\n", + " pertinence (cosinus d'embeddings).\n", + "- La **réflexion** transforme des observations en pensées citées, qui retournent dans\n", + " le flux : le papier montre que sans elle, la believabilité mesurée chute\n", + " ($\\mu$ 29,89 → 26,88) et, à l'échelle de Smallville, l'information circule deux fois\n", + " moins bien. Ces chiffres sont ceux du papier, pas ce carnet : ici la brique **a\n", + " déclenché** — 5 réflexions (une par agent) et 15 insights sur deux jours de jeu,\n", + " sorties par la cellule de simulation — là où le papier compte 2 à 3 réflexions\n", + " quotidiennes par agent.\n", + "- La **planification** en blocs récursifs, stockée comme mémoire, donne au comportement\n", + " sa cohérence dans le temps — c'est elle qui empêche Klaus de déjeuner trois fois.\n", + "- Tout cela s'exécute ici sur un stack **local et reproductible** (vLLM + encodeur\n", + " hors-ligne) : aucune clé cloud, aucun contenu fabriqué à la main.\n", + "\n", + "**Référence principale** : Park, J.S., O'Brien, J.C., Cai, C.J., Morris, M.R., Liang, P.,\n", + "Bernstein, M.S. (2023). *Generative Agents: Interactive Simulacra of Human Behavior*.\n", + "UIST '23. arXiv:2304.03442. Copie archivée : `G:\\Mon Drive\\MyIA\\IA\\Bibliographie IA\\AI\\2023\n", + "- Park O'Brien Cai - Generative Agents - Interactive Simulacra of Human Behavior (arXiv\n", + "2304.03442).pdf`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.15" + }, + "papermill": { + "default_parameters": {}, + "duration": 2469.227421, + "end_time": "2026-11-04T09:25:12.553074+00:00", + "environment_variables": {}, + "exception": null, + "input_path": "24_Generative_Agents_MiniMonde.ipynb", + "output_path": "24_Generative_Agents_MiniMonde.ipynb", + "parameters": {}, + "start_time": "2026-11-04T08:44:03.325653+00:00", + "version": "2.7.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "1f8637285b954494baa7ab3f4a951958": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_c59a806839e94b92a326391a56b0f824", + "placeholder": "​", + "style": "IPY_MODEL_4b9301b96f874ae9b4b248d329161e89", + "tabbable": null, + "tooltip": null, + "value": " 199/199 [00:00<00:00, 7041.34it/s]" + } + }, + "4b9301b96f874ae9b4b248d329161e89": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "6cb85530ceac4ac6941c1844ddf4db70": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_df94fb50595240f2a568fbed90c393c1", + "IPY_MODEL_e3862a2f3d8f449a852fdad4e5c72e7a", + "IPY_MODEL_1f8637285b954494baa7ab3f4a951958" + ], + "layout": "IPY_MODEL_f74c49197707444d8eb8ad189110c951", + "tabbable": null, + "tooltip": null + } + }, + "99c650f2eae945c0b67af3e0af071107": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c2786e85a06741f9a3cc35d06c4df0e5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c59a806839e94b92a326391a56b0f824": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cc4d245e3224484ca1485761d6204b61": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "df50aaab009b4391a8e2b97a5d39d6b3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "df94fb50595240f2a568fbed90c393c1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_c2786e85a06741f9a3cc35d06c4df0e5", + "placeholder": "​", + "style": "IPY_MODEL_cc4d245e3224484ca1485761d6204b61", + "tabbable": null, + "tooltip": null, + "value": "Loading weights: 100%" + } + }, + "e3862a2f3d8f449a852fdad4e5c72e7a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_99c650f2eae945c0b67af3e0af071107", + "max": 199.0, + "min": 0.0, + "orientation": "horizontal", + "style": "IPY_MODEL_df50aaab009b4391a8e2b97a5d39d6b3", + "tabbable": null, + "tooltip": null, + "value": 199.0 + } + }, + "f74c49197707444d8eb8ad189110c951": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MyIA.AI.Notebooks/GenAI/Texte/README.md b/MyIA.AI.Notebooks/GenAI/Texte/README.md index 2c54300d2ac..77330541c41 100644 --- a/MyIA.AI.Notebooks/GenAI/Texte/README.md +++ b/MyIA.AI.Notebooks/GenAI/Texte/README.md @@ -142,6 +142,14 @@ Le notebook **23** franchit le dernier mur : le pipeline qui vivait dans des cel |---|----------|-------------|-------| | 23 | `23_Du_Notebook_A_L_Application.ipynb` | **Servir le RAG du 05** : extraction fidèle du pipeline (corpus Lincoln-Douglas, chunking, embeddings, k-NN, génération citée) puis passage au service — API **FastAPI** (`/health`, `/query`) sur port dynamique testée par appels `httpx` réels, UI **Gradio** passant uniquement par l'API (test des sources déterministes : deux canaux, mêmes chunks), mesure du coût par couche (transport pur de l'ordre de la milliseconde, surcout `/query` noyé dans la variance de génération — négatif sur 2 questions sur 3 au run commis), normalisation des scalaires numpy pour le contrat JSON, arrêt propre des services, 3 exercices C.1 | — | +### Tier 9 : Agents génératifs — mémoire, réflexion et planification + +Le carnet **24** descend d'un cran dans l'échelle après le service du tier 8 : il simule un **petit monde** — 5 agents sur une grille 5×5, deux jours de jeu — d'après Park et al. 2023 (*Generative Agents: Interactive Simulacra of Human Behavior*, UIST '23, arXiv 2304.03442). L'architecture du papier est reproduite terme à terme sur le vrai moteur : flux de mémoire noté par le LLM à la création, récupération à trois composantes (récence qui décroît de 0,995 par heure de jeu depuis le dernier accès, importance 1-10, pertinence par similarité d'embeddings), réflexion au seuil d'importance cumulée dont les insights citent leurs preuves, planification journalière rejouée chaque matin, et boucle d'action où un dialogue n'existe que si deux agents partagent une case. L'expérience de propagation du papier est rejouée à notre échelle, avec sa vérification anti-hallucination : un agent qui « dit » savoir ne compte que si sa mémoire porte effectivement l'information. Au run commis, 50 appels LLM réels au stack vLLM local donnent une propagation de 40 % sur le vernissage et 0 % sur la candidature, contre 52 % et 32 % chez les 25 agents du papier — l'écart est un effet d'échelle, et le carnet dit lequel. 3 exercices C.1. + +| # | Notebook | Description | Durée | +|---|----------|-------------|-------| +| 24 | `24_Generative_Agents_MiniMonde.ipynb` | **Simuler un petit monde** : 5 agents, grille 5×5, deux jours — flux de mémoire noté par le LLM, récupération à trois composantes (récence 0,995/h, importance, pertinence par embeddings), réflexion au seuil dont les insights citent leurs preuves, planification journalière, boucle d'action avec dialogues conditionnés par la récupération propre à chaque agent ; expérience de propagation rejouée avec vérification anti-hallucination — au run commis 50 appels LLM réels, vernissage 40 % / candidature 0 % contre 52 % / 32 % au papier (écart d'échelle, mesuré), 3 exercices C.1 | 60 min | + ## Prérequis ### Configuration API