Bases de lecture d’un CSV avec pandas (read_csv, filtrage boolean)
Aucune cle API requise (ce rung est 100% déterministe, zero appel LLM)
Note anti-theatre : ce rung charge reellement le CSV et execute un vrai filtrage pandas. Aucune sortie n’est simulee. Le detecteur par mots-cles est volontairement simpliste : son but est pedagogique (montrer la couche de matching), pas d’atteindre une haute precision. L’extraction sémantique robuste est l’objet des rungs suivants (LLM + logique formelle).
Confidentialite : tous les exemples sont synthetiques et neutres (meteorologie, oiseaux, Socrate, phrases abstraites). Aucun corpus EPITA, aucun nom d’etudiant, aucun texte sensible. Le depot est public sur GitHub.
1. Introduction : la taxonomie des sophismes
Un sophisme (fallacy) est un argument qui semble valide mais qui ne l’est pas, soit parce que les premisses ne soutiennent pas la conclusion, soit parce qu’il manipule l’auditoire. Pour detecter automatiquement un sophisme, il faut d’abord une taxonomie : un arbre hierarchise qui classe les sophismes par familles, sous-familles, etc.
Ce notebook utilise la taxonomie du projet Argumentum (1408 entrees, post-resync verbatim upstream ac33f607 – voir data/NOTICE et data/NOTICE-VIRTUES). Une taxonomie soeur des vertus argumentatives (argumentum_virtues_taxonomy.csv, 223 cartes) vit dans le même dossier et partage la même structure d’arbre depth 0..7. Les deux CSV sont organises comme des arbres de profondeur variable :
depth
Rôle
Exemple
0
Meta-noeud racine
“Argument fallacieux” (1 seule ligne, a exclure)
1
7 familles de premier niveau
Insuffisance, Influence, Obstruction, …
2
Sous-familles
Obstruction -> {Refus du debat, Sabotage, Ad hominem}
3+
Sophismes spécifiques
Ad hominem -> circonstanciel, abusif, …
Stance anti-theatre : on charge le CSV avec pandas.read_csv et on filtre reellement les lignes. Le detecteur de la section 3 tourne sur de vraies listes de mots-cles appliquees a un texte synthetique — jamais de résultat en dur.
Étape
Outil
Ce qu’il fait
Texte naturel -> sophisme identifie
Detecteur mots-cles (ce rung)
Matching chaîne, déterministe, peu précis
Texte naturel -> structure informelle
LLM (rung 3)
Extraction sémantique probabiliste
Structure -> verification logique
Tweety (rung 2)
Preuve sound & complete
2. Charger la taxonomie et identifier les 7 familles
La cellule ci-dessous charge le CSV avec pandas.read_csv(..., dtype=str) — tout est traite comme chaîne pour eviter les coercitions numériques sur les colonnes depth, path, etc. On filtre ensuite depth == '1' pour obtenir les familles de premier niveau.
Pourquoi exclure depth == '0' ? Il existe un meta-noeud racine “Argument fallacieux” (1 ligne) qui represente le concept general de sophisme. Ce n’est pas une famille operationnelle : on l’evicte pour ne garder que les 7 vraies familles.
# Cellule [2] - Charger la taxonomie via la shim argumentation_libimport pandas as pd# Le package argumentation_lib vit a cote du notebook ; il est importable# directement (le dossier du notebook est sur sys.path au runtime). La shim# resout le dossier data/ via __file__ (cwd-independant) -- on evite ainsi le# chemin relatif 'data/...' qui leverait FileNotFoundError hors de ce dossier.# Aucun guard de path dans la cellule : la robustesse cwd est portee par la# shim (_paths.py, __file__-relative), comme au rung 2-formal (#3857).from argumentation_lib import get_data_dirCSV_PATH = get_data_dir() /"argumentum_fallacies_taxonomy.csv"print(f"Taxonomie resolue via la shim : {CSV_PATH.name}")df = pd.read_csv(CSV_PATH, dtype=str, encoding="utf-8-sig") # BOM UTF-8 stripped (canonical upstream ac33f607)# La racine depth=0 est un meta-noeud (1 ligne) : on l'exclut.meta_root = df[df['depth'] =='0']print(f"Total lignes CSV : {len(df)}")print(f"Meta-noeud depth=0 : {len(meta_root)} ligne(s) -> exclue(s) de la liste des familles")# Les 7 familles de premier niveaufamilles = df[df['depth'] =='1'][['Famille', 'desc_fr']].reset_index(drop=True)print(f"Familles depth=1 : {len(familles)}")print()print('=== Les 7 familles de sophismes (depth=1) ===')for i, row in familles.iterrows(): num = i +1 desc = row['desc_fr'] if pd.notna(row['desc_fr']) else'(pas de description)'print(f" {num}. {row['Famille']:<25} -- {desc}")
Taxonomie resolue via la shim : argumentum_fallacies_taxonomy.csv
Total lignes CSV : 1408
Meta-noeud depth=0 : 1 ligne(s) -> exclue(s) de la liste des familles
Familles depth=1 : 7
=== Les 7 familles de sophismes (depth=1) ===
1. Insuffisance -- Les arguments que vous utilisez ne suffisent pas à démontrer votre propos.
2. Influence -- Vous manipulez votre auditoire pour le persuader au lieu de le convaincre.
3. Erreur mathématique -- Votre raisonnement comporte des inexactitudes quantitatives.
4. Erreur de raisonnement -- Votre thèse repose sur un raisonnement incohérent.
5. Abus de langage -- Vous usez de subtilités linguistiques pour convaincre.
6. Tricherie -- Vous vous affranchissez des règles tacites qui régissent un débat rationnel.
7. Obstruction -- Par un artifice, oratoire ou autre, vous faites en sorte que la discussion ne se déroule pas comme prévu.
Interpretation : les 7 familles de la taxonomie
Sortie obtenue : 7 familles de depth=1, chacune avec une description courte.
Famille
Idee générale
Insuffisance
Les premisses ne suffisent pas a prouver la conclusion
Influence
Manipulation emotionnelle de l’auditoire au lieu d’argumenter
Contournement des règles tacites du debat rationnel
Obstruction
La discussion est empechee de se derouler normalement
Point cle : le meta-noeud depth=0 (“Argument fallacieux”, 1 ligne) n’est PAS une famille. C’est la racine conceptuelle de tout l’arbre. Les 7 familles operationnelles commencent a depth=1.
# Cellule [4-bis] - Charger la taxonomie soeur des VERTUS argumentatives## Meme repertoire, meme upstream ac33f607, schema distinct delibere.# On utilise la MEME shim cwd-robuste ; on dispatche sur le filename.# Note pedagogique : les colonnes "snake_case" (family_fr) sont incompatibles# avec le "PascalCase" du CSV Fallacies (Famille). Tout consumer qui# presume un schema unifie tombera sur un KeyError -- d'ou l'importance du# dispatch filename-based documente en data/NOTICE-VIRTUES.VERT_PATH = get_data_dir() /"argumentum_virtues_taxonomy.csv"df_vert = pd.read_csv(VERT_PATH, dtype=str, encoding="utf-8-sig") # BOM UTF-8 stripped# Distribution des profondeurs (Vertues 0..7, vs Fallacies 0..10)depth_dist_vert = df_vert['depth'].value_counts().sort_index()print(f"Taxonomie Vertus resolue via la shim : {VERT_PATH.name}")print(f"Total cartes Vertus : {len(df_vert)} (Fallacies: {len(df)} -- facteur ~6.3x)")print(f"Distribution des profondeurs (Vertues 0..7) :")for d, n in depth_dist_vert.items():print(f" depth={d} -> {n} cartes")print()print("=== Premiers vertus (depth=1, familles de premier niveau) ===")vert_familles = df_vert[df_vert['depth'] =='1'][['family_fr', 'description_fr']].reset_index(drop=True)for i, row in vert_familles.iterrows(): num = i +1 desc = row['description_fr'] if pd.notna(row['description_fr']) else'(pas de description)'print(f" {num}. {row['family_fr']:<25} -- {desc}")
Taxonomie Vertus resolue via la shim : argumentum_virtues_taxonomy.csv
Total cartes Vertus : 223 (Fallacies: 1408 -- facteur ~6.3x)
Distribution des profondeurs (Vertues 0..7) :
depth=0 -> 1 cartes
depth=1 -> 7 cartes
depth=2 -> 21 cartes
depth=3 -> 63 cartes
depth=4 -> 69 cartes
depth=5 -> 28 cartes
depth=6 -> 15 cartes
depth=7 -> 19 cartes
=== Premiers vertus (depth=1, familles de premier niveau) ===
1. Argument pertinent -- Argument adapté au contexte, appuyé par des preuves utiles à la conclusion.
2. Présentation intègre -- Principe d’une argumentation transparente et honnête, sans sophismes ni manipulations.
3. Sens quantitatif -- Employer les mathématiques avec précision pour structurer et analyser des arguments ou des données.
4. Inférence maîtrisée -- Méthode rigoureuse qui articule des prémisses acceptables et des règles d’inférence valides pour aboutir à une conclusion cohérente et justifiée.
5. Justesse lexicale -- Usage précis et approprié du langage pour éviter les ambiguïtés et les erreurs de communication.
6. Honnêteté intellectuelle -- Traiter honnêtement et avec neutralité vos sources, objectifs, positions et conclusions, afin de respecter les règles communes du débat ou de l’argumentation.
7. Échange enrichissant -- Dialogue structuré où l’écoute active et la considération mutuelle font progresser vers une entente ou une compréhension plus fine.
Interpretation : divergence de schema entre Fallacies et Vertues
Sortie obtenue : 223 cartes Vertus, structure depth=0..7, avec une distribution pedagogiquement interessante (1 racine / 7 familles / 21 sous-familles / 63 ss-sous-familles, puis 69/28/15/19 aux niveaux 4..7). Cette distribution suit la même progression que les Fallacies pour les 3 premiers niveaux (1/7/21/63) mais diverge au-dela – les Vertues sont un arbre moins profond et moins dense.
Implication consumer : le family_fr des Vertues N’EXISTE PAS comme Famille dans les Fallacies (la cle est en snake_case, pas PascalCase). Le text_fr multi-field des Fallacies N’EXISTE PAS comme title_fr/description_fr dans les Vertues. Tout code qui presume un schema unifie tombera sur un KeyError. Les loaders doivent dispatcher sur le filename (comme ici : Fallacies via argumentum_fallacies_taxonomy.csv, Vertues via argumentum_virtues_taxonomy.csv), pas assumer une liste de colonnes commune. Cette divergence est documentee verbatim dans data/NOTICE-VIRTUES pour eviter la surprise silencieuse cote consumer.
Pourquoi encoding="utf-8-sig" ici aussi ? Même raison que pour les Fallacies (cell [3]) : les deux CSV sont consignes verbatim upstream ac33f607 avec leur BOM UTF-8 d’origine (bytes 0xEF 0xBB 0xBF en debut de fichier). Pandas 2.x detecte ce BOM automatiquement avec encoding='utf-8', mais specifier encoding='utf-8-sig' est explicite et documente (per ai-01 design-gate msg-20260703T110001-uqono0, 2026-07-03) et permet une portabilite vers d’autres readers Python (csv.reader, open().readlines()).
3. Descente d’un niveau (depth=2) : la famille Obstruction
Pour comprendre comment l’arbre se ramifie, on choisit une famille et on regarde ses sous-familles directes (depth=2). Prenons Obstruction : la famille qui regroupe les sophismes ou l’on empeche le debat d’avoir lieu normalement.
On filtre avec deux conditions combinees par & : Famille == 'Obstruction' ET depth == '2'. Le résultat est un sous-DataFrame qu’on affiche avec ses colonnes utiles.
# Cellule [4] - Descente d'un niveau : sous-familles depth=2 de la famille Obstruction# On filtre le DataFrame principal : famille choisie ET depth=2.FAMILLE_DEMO ='Obstruction'sous_familles = df[(df['Famille'] == FAMILLE_DEMO) & (df['depth'] =='2')][ ['path', 'Famille', 'Sous-Famille', 'text_fr', 'example_fr']].reset_index(drop=True)print(f"=== Sous-familles depth=2 de '{FAMILLE_DEMO}' ({len(sous_familles)} trouvees) ===")for i, row in sous_familles.iterrows(): text_fr = row['text_fr'] if pd.notna(row['text_fr']) else'(sans nom)' example = row['example_fr'] if pd.notna(row['example_fr']) else'(pas d exemple)' ex_str =str(example)print(f"\n [{row['path']}] {text_fr}")print(f" Exemple : {ex_str[:90]}{'...'iflen(ex_str) >90else''}")
=== Sous-familles depth=2 de 'Obstruction' (3 trouvees) ===
[7.1] Refus du débat
Exemple : Mes positions sont claires, connues de tous, et je n’ai pas à en débattre.
[7.2] Sabotage du débat
Exemple : Allons plutôt dans mon bureau discuter de l’absurdité de votre demande d’augmentation…
[7.3] Ad hominem
Exemple : Comment pouvez-vous prétendre que votre politique de santé fonctionnera quand vous n’avez ...
Interpretation : la ramification depth=2
Sortie obtenue : 3 sous-familles pour Obstruction, identifiees par leur path hiérarchique.
path
Sous-famille
Idee
7.1
Refus du debat
Refuser d’argumenter (positions non negociables)
7.2
Saboter le debat (Sabotage)
Detourner la discussion, la rendre impossible
7.3
Ad hominem
Attaquer la personne plutot que l’argument
Points cles : 1. Le path (ex 7.1, 7.2) encode la position hiérarchique : le 7 correspond a la 7e famille de depth=1 (Obstruction), le suffixe .N a la Neme sous-famille. 2. Chaque sous-famille peut elle-même se ramifier en depth=3 (ex : Ad hominem -> circonstanciel, abusif, preemptif). L’arbre a jusqu’a 10 niveaux pour certains sophismes très spécifiques. 3. La colonne example_fr donne un exemple d’usage — utile pour calibrer un detecteur ou entrainer un classifieur.
Exercice 1 : descendre d’un niveau pour une AUTRE famille
Contexte : on vient de descendre Obstruction. Le même filtre pandas fonctionne pour n’importe quelle famille.
Objectif : choisissez une famille parmi {Insuffisance, Influence, Erreur mathematique, Erreur de raisonnement, Abus de langage, Tricherie} et affichez ses sous-familles depth=2 (nom + exemple). Retournez le nombre de sous-familles trouvees.
Indices : - Étape 1 : fixer FAMILLE_EX1 a un nom parmi les 6 autres familles - Étape 2 : reutiliser le filtre df[(df['Famille'] == FAMILLE_EX1) & (df['depth'] == '2')] - Étape 3 : iterer et afficher chaque sous-famille avec son text_fr - Étape 4 : retourner/compter le nombre de sous-familles
# Exercice 1 : descendre d'un niveau pour une autre famille que Obstruction.# Etape 1 : choisir une famille (parmi Insuffisance, Influence, Erreur mathematique,# Erreur de raisonnement, Abus de langage, Tricherie).FAMILLE_EX1 =None# TODO etudiant : remplacer par le nom d'une famille# Etape 2-3 : filtrer et afficher les sous-familles depth=2 de cette famille.# TODO etudiant : reprendre le filtre de la cellule demo avec FAMILLE_EX1.# Etape 4 : nombre de sous-familles trouveesnb_sous_familles_ex1 =None# TODO etudiant : len(...) du DataFrame filtreprint(f"Exercice 1 : {FAMILLE_EX1} a {nb_sous_familles_ex1} sous-famille(s) depth=2")print("Exercice a completer")
Exercice 1 : None a None sous-famille(s) depth=2
Exercice a completer
4. Detection déterministe par mots-cles sur un texte synthetique
La descente taxonomique nous donne un referentiel de noms de sophismes. On construit maintenant un detecteur : etant donne un texte, quel sophisme de la taxonomie est probablement present ?
Ce detecteur est déterministe et zero-LLM : il s’agit d’un simple dictionnaire {nom_sophisme: [mots_cles]}. Si un mot-cle apparait (insensible a la casse) dans le texte, le sophisme correspondant est signale. Ce couche est volontairement simpliste — sa limite (faible precision, aucun sens contextuel) est exactement ce qui motivera l’introduction du LLM dans les rungs suivants.
Important : on travaille sur des exemples synthetiques (phrases abstraites construites pour la demonstration). Aucun texte reel, aucune donnee sensible.
# Cellule [6] - Detecteur deterministe par mots-cles + test sur un texte synthetique## Audit #18391 : la version precedente declarait un dico a 4 entrees en dur, sans# aucun ancrage dans la taxonomie de 1408 noeuds chargee par c.3. On construit# maintenant la table depuis `df` : pour chaque noeud qui porte un# `nom_vulgarise` non vide, on prend 3 tokens discriminants de `desc_fr`# (longueur >= 5, alphabetique, lowercase, hors stopwords). 38 sophismes# couverts au lieu de 4 -- c'est la portee reelle de la taxonomie.import re_STOPWORDS = {"vous", "votre", "cette", "celui", "celle", "celles", "votre","leurs", "leurs", "toute", "tous", "etre", "ayant", "sont", "sommes","faire", "soit", "meme", "alors", "dans", "pour", "sans", "sous","avec", "mais", "donc", "plus", "moins", "entre", "comme", "aussi","leur", "bien", "peut", "etre",}def _build_detecteur(taxonomy_df, max_keywords=3, min_len=5):"""Construit la table {nom_vulgarise: (Famille, Sous-Famille, [keywords])}. Les keywords sont des tokens de `desc_fr` (lowercase, alpha, longueur >= ``min_len``, hors stopwords) pris dans l'ordre d'apparition (jusqu'a `max_keywords`), sans tri -- la selection des tokens discriminants reste une borne du detecteur (voir Limite 2 de la cellule d'interpretation). """ table = {}for _, row in taxonomy_df.iterrows(): nom = row.get("nom_vulgarisé")# Filtrer les entrees polluees du CSV upstream (PK 993 porte un# commentaire CSV dans la colonne nom_vulgarise).ifnotisinstance(nom, str) ornot nom.strip() orlen(nom.strip()) >50:continue desc = row.get("desc_fr") or"" tokens = [ tokfor tok in re.findall(r"[a-zA-ZaeiouyAEIOUY]{%d,}"% min_len, desc.lower())if tok notin _STOPWORDS ]# Dedup en gardant l'ordre seen =set() kws = []for tok in tokens:if tok in seen:continue seen.add(tok) kws.append(tok)iflen(kws) >= max_keywords:breakifnot kws:continue famille = row.get("Famille") or"" sous_famille = row.get("Sous-Famille") or"" table[nom.strip()] = (famille, sous_famille, kws)return tableDETECTEUR_SOPHISMES = _build_detecteur(df)print(f"Detecteur : {len(DETECTEUR_SOPHISMES)} sophismes couverts depuis la taxonomie.")print(f"Exemples : {list(DETECTEUR_SOPHISMES.keys())[:5]}")def detecter_sophismes(texte, table=DETECTEUR_SOPHISMES):"""Retourne la liste des sophismes detectes dans `texte`. Chaque element est un dict {sophisme, famille, sous_famille, mot_cle_trigger}. Aucun appel LLM : c'est du filtrage de chaine pur sur les ``keywords`` produits par ``_build_detecteur``. """ texte_lower = texte.lower() matches = []for nom_sophisme, (famille, sous_famille, mots_cles) in table.items():for mot in mots_cles:if mot in texte_lower: matches.append({"sophisme": nom_sophisme,"famille": famille,"sous_famille": sous_famille,"mot_cle_trigger": mot, })break# un seul trigger par sophisme suffitreturn matches# Texte synthetique neutre : pas de personne reelle, pas d opinion politique.TEXTE_SYNTHETIQUE = ("Vous etes incompetent pour juger, et de toute facon je rejette ""toute information qui pourrait contredire ma position.")detections = detecter_sophismes(TEXTE_SYNTHETIQUE)print("--- Texte synthetique analyse ---")print(TEXTE_SYNTHETIQUE)print()print(f"--- Sophismes detectes ({len(detections)}) ---")for d in detections[:10]:print(f" - {d['sophisme']:<35} (famille={d['famille']}, sous_famille={d['sous_famille']}) "f"trigger='{d['mot_cle_trigger']}'")iflen(detections) >10:print(f" ... et {len(detections) -10} autre(s)")
Detecteur : 39 sophismes couverts depuis la taxonomie.
Exemples : ['La tête dans le sable', 'Répulsif anti-girafe', 'Trouver des excuses', 'Inertie mentale', 'Inventer des salades']
--- Texte synthetique analyse ---
Vous etes incompetent pour juger, et de toute facon je rejette toute information qui pourrait contredire ma position.
--- Sophismes detectes (4) ---
- La tête dans le sable (famille=Insuffisance, sous_famille=Argument bâclé) trigger='information'
- Versatilité (famille=Tricherie, sous_famille=Changement de cap) trigger='position'
- Rétropédalage (famille=Tricherie, sous_famille=Changement de cap) trigger='position'
- Biais culturel (famille=Tricherie, sous_famille=Raisonnement biaisé) trigger='position'
Interpretation : sortie du detecteur
Sortie obtenue : 3 sophismes signales sur le texte synthetique, chacun avec son trigger issu du matching mots-cles sur la table construite depuis la taxonomie.
Sophisme
Famille / Sous-Famille
Trigger
Lecture
La tete dans le sable
Insuffisance / Argument bâclé
information
Refuser l’information qui contredit sa position est une posture d’evitement
Retropedalage
Tricherie / Changement de cap
position
Rejeter en bloc ce qui menace sa position sans examiner le fond
Biais culturel
Tricherie / Pensee biaisee
position
Rejeter un argument au nom d’une appartenance culturelle presupposee
Limites volontaires de ce detecteur : 1. Pas de contexte : le matching mots-cles ignore la semantique ; information peut designer un fait neutre, pas un refus. Une phrase contenant le mot declenche le sophisme independamment du sens. 2. Couverture derivee de la taxonomie : la table est construite dynamiquement a partir de desc_fr (3 tokens discriminants par noeud, longueur >= 5, hors stopwords), pas en dur. Le compte depend du nombre de noeuds dont la colonne nom_vulgarise est exploitable (38 a ce snapshot). La discrimination reste limitee par la qualite de desc_fr (des tokens generiques comme position peuvent tirer plusieurs sophismes incompatibles sur la meme phrase — c’est une borne, pas un defaut du generateur). 3. Aucun score de confiance : un match est binaire (present / absent). Pas de probabilite ni de ranking.
Lien avec les rungs suivants : le rung 2 (logique formelle) verifie si un argument est valide ; le rung 3 (orchestration LLM) remplace la table mots-cles par une extraction sémantique robuste. Ce rung montre la ligne de base (baseline) que ces techniques cherchent a ameliorer.
Exercice 2 : ajouter une règle de detection pour un sophisme au choix
Contexte : la table DETECTEUR_SOPHISMES est construite dynamiquement a partir de la taxonomie (38 entrees a ce snapshot, derivees des noeuds dont nom_vulgarise est exploitable). Pour ajouter un sophisme absent de la table, la voie directe est d’enrichir la taxonomie source : le generateur reconstruira la table au prochain appel de _build_detecteur(df).
Objectif : ajoutez une nouvelle entree directement dans le dictionnaire DETECTEUR_SOPHISMES (un sophisme de votre choix parmi ceux de la taxonomie), definissez 2-3 mots-cles pertinents, puis testez votre detecteur etendu sur une phrase synthetique que vous ecrivez. La forme du tuple (famille, sous_famille, [keywords]) reste inchangee.
Exemples de sophismes candidats (a chercher dans le CSV filtre par nom_vulgarisé) : Pente glissante, Homme de paille, Appel a l’emotion, Argument circulaire.
Indices : - Étape 1 : copier DETECTEUR_SOPHISMES dans une nouvelle variable table_etendue - Étape 2 : ajouter une cle avec un tuple (famille, sous_famille, [mots_cles]) - Étape 3 : ecrire une PHRASE_TEST synthetique contenant un des mots-cles - Étape 4 : appeler detecter_sophismes(PHRASE_TEST, table_etendue) et afficher
# Exercice 2 : etendre le detecteur et tester sur une phrase synthetique.# Etape 1 : copier la table existantetable_etendue =dict(DETECTEUR_SOPHISMES) # TODO etudiant : point de depart# Etape 2 : ajouter un nouveau sophisme# TODO etudiant : table_etendue['Mon sophisme'] = ('Famille', 'Sous-famille', ['mot1', 'mot2'])# Etape 3 : phrase synthetique de testPHRASE_TEST =None# TODO etudiant : une phrase contenant un des nouveaux mots-cles# Etape 4 : detection et affichagedetections_ex2 = [] # TODO etudiant : detecter_sophismes(PHRASE_TEST, table_etendue)print(f"Exercice 2 : {len(detections_ex2)} sophisme(s) detecte(s)")print("Exercice a completer")
Exercice 2 : 0 sophisme(s) detecte(s)
Exercice a completer
Exercice 3 : fonction qui retourne la famille du premier sophisme detecte
Contexte : on veut parfois une reponse simple — “quelle est la famille dominante de ce texte ?” — plutot qu’une liste de tous les matches.
Objectif : ecrivez une fonction famille_premier_sophisme(texte) qui retourne le nom de la famille du premier sophisme detecte dans texte, ou None si aucun match. Utilisez detecter_sophismes définie plus haut.
Indices : - Étape 1 : appeler detecter_sophismes(texte) pour obtenir la liste des matches - Étape 2 : si la liste est vide, retourner None - Étape 3 : sinon, retourner matches[0]['famille'] - Étape 4 : tester sur deux phrases (une qui match, une qui ne match pas)
# Exercice 3 : famille du premier sophisme detecte.def famille_premier_sophisme(texte):# TODO etudiant : utiliser detecter_sophismes(texte) et retourner# la famille du premier element, ou None si la liste est vide.# Etape 1 : matches = [] # TODO etudiant : detecter_sophismes(texte)# Etape 2-3 :returnNone# TODO etudiant : remplacer par la logique# Etape 4 : testsphrase_avec ="Tous les chats sont noirs, c'est bien connu."phrase_sans ="Le ciel est bleu aujourd'hui."famille_avec = famille_premier_sophisme(phrase_avec)famille_sans = famille_premier_sophisme(phrase_sans)print(f"Phrase avec sophisme -> famille : {famille_avec}")print(f"Phrase sans sophisme -> famille : {famille_sans}")print("Exercice a completer")
Phrase avec sophisme -> famille : None
Phrase sans sophisme -> famille : None
Exercice a completer
5. Pont vers l’etat partage (shim argumentation_lib)
Jusqu’ici, les detections de la section 4 vivent dans une liste Python locale. Dans le pipeline complet de la serie, les analyses (informelle, formelle, orchestree) partagent un etat unique – l’objet RhetoricalAnalysisState fourni par la shim argumentation_lib. C’est l’objet que le rung 3-orchestration remplit via un agent LLM, et que le rung 2-formal enrichit de résultats de preuve.
Cette section montre le geste fondamental du pipeline : nourrir cet etat partage a partir de notre detecteur déterministe – sans LLM. On retrouve ainsi, cote a cote, les mêmes structures de données que les rungs ulterieurs manipuleront.
Champ de l’etat
Rempli ici (detecteur mots-cles)
Rempli au rung 3 (LLM)
raw_text
le texte synthetique
un texte reel fourni en entree
identified_arguments
un argument de demonstration
les arguments extraits semantiquement
identified_fallacies
les detections + leur famille
les sophismes extraits par le LLM
Pourquoi anticiper cette structure ? La valeur d’un pipeline multi-agents tient dans l’etat partage : chaque agent (detection, formalisation, synthese) lit et enrichit le même objet. Brancher notre baseline dessus rend cette architecture concrete bien avant d’introduire le LLM.
# Cellule [section 5] - Pont vers l'etat partage : nourrir RhetoricalAnalysisState## Le detecteur mots-cles (section 4) produit une liste Python locale. Dans le# pipeline complet de la serie, les analyses (informelle, formelle, orchestree)# partagent un ETAT UNIQUE -- l'objet RhetoricalAnalysisState de la shim# argumentation_lib. Le rung 3 le remplit via un agent LLM ; le rung 2 y attache# des resultats de preuve. Ici on montre le geste fondamental : nourrir cet# etat a partir de notre detecteur deterministe, SANS LLM.import loggingfrom argumentation_lib import RhetoricalAnalysisState# Rendre les logs internes de l'etat silencieux (niveau WARNING) pour un output propre.logging.getLogger("RhetoricalAnalysisState").setLevel(logging.WARNING)etat = RhetoricalAnalysisState(initial_text=TEXTE_SYNTHETIQUE)arg_id = etat.add_argument("Argument synthetique analyse par le detecteur mots-cles")for d in detections: etat.add_fallacy( fallacy_type=d["sophisme"], justification=f"declencheur mot-cle : '{d['mot_cle_trigger']}'", target_arg_id=arg_id, family=d["famille"], taxonomy_path=d["sous_famille"], )snapshot = etat.get_state_snapshot(summarize=True)print("--- Etat partage apres detection mots-cles ---")print(f" raw_text : {snapshot['raw_text_snippet']}")print(f" arguments identifies : {snapshot['argument_count']} -> {list(etat.identified_arguments)}")print(f" sophismes identifies : {snapshot['fallacy_count']}")for fid, fdata in etat.identified_fallacies.items():print(f" {fid} : {fdata['type']:<25} (famille={fdata.get('family','?')}, cible={fdata.get('target_argument_id','?')})")print()print("Cet etat est exactement la structure que le rung 3 (orchestration LLM)")print("remplira via un agent, et que le rung 2 (formel) enrichira de resultats de preuve.")
--- Etat partage apres detection mots-cles ---
raw_text : Vous etes incompetent pour juger, et de toute facon je rejette toute information qui pourrait contredire ma position.
arguments identifies : 1 -> ['arg_1']
sophismes identifies : 4
fallacy_1 : La tête dans le sable (famille=Insuffisance, cible=arg_1)
fallacy_2 : Versatilité (famille=Tricherie, cible=arg_1)
fallacy_3 : Rétropédalage (famille=Tricherie, cible=arg_1)
fallacy_4 : Biais culturel (famille=Tricherie, cible=arg_1)
Cet etat est exactement la structure que le rung 3 (orchestration LLM)
remplira via un agent, et que le rung 2 (formel) enrichira de resultats de preuve.
6. Conclusion et recapitulatif
Ce rung a pose les fondations taxonomiques de la detection de sophismes : on charge un referentiel structure (le CSV), on descend dans l’arbre par filtre pandas, puis on construit une couche de matching mots-cles. La section 5 a de plus branche cette baseline sur l’etat partage de la serie (RhetoricalAnalysisState). Aucune LLM, aucune simulation : tout est déterministe et reproductible.
Pas d’unified schema inter-CSV – KeyError si presume
Descendre d’un niveau
Filtre booléen (Famille == X) & (depth == '2')
Identification des sous-familles directes
Manuelle, famille par famille
Detection mots-cles
Dictionnaire {sophisme: [mots]} + in
Déterministe, zero API, transparent
Pas de contexte, faible precision
Etats partage
RhetoricalAnalysisState.add_fallacy(...)
Branche la baseline sur le pipeline multi-agents
La structure reste vide tant qu’aucun agent LLM ne l’enrichit
Points a retenir
La taxonomie est un arbre : depth encode le niveau (0=racine meta, 1=familles, 2=sous-familles, 3+=instances spécifiques). Le path (ex 7.3) encode la position hiérarchique.
Le detecteur mots-cles est une baseline : il montre ce qu’on peut faire sans LLM, et donc ce que le LLM devra ameliorer. Une limite importante est l’absence de contexte (tous les oiseaux declencherait une fausse “Generalisation abusive”).
Confidentialite par design : tous les exemples sont synthetiques. Pour un usage reel, le pipeline complet (rung 3) orchestre l’extraction LLM puis la verification formelle (rung 2) – jamais de stockage de texte sensible dans le depot public.
L’etat partage est le lien entre les rungs : RhetoricalAnalysisState est le conteneur commun. Ce rung (detection), le rung 2 (preuve) et le rung 3 (orchestration LLM) y lisent et ecrivent. Brancher notre baseline des maintenant rend cette architecture concrete.
Prochaines étapes
2-formal : une fois un argument identifie, verifier sa validite logique avec le raisonneur Tweety (PL, FOL, modal, argumentation de Dung).
3-orchestration : remplacer la table mots-cles par un LLM qui extrait la structure informative, puis deleguer la verification a Tweety.
7. Entonnoir taxonomique (portee 2/3 de #18391)
La section 4 a utilise une baseline deterministe : un lexique mots-cles sur la taxonomie du tronc EPITA, avec un overhead Python minimal. La section 5 l’a rebranchee sur l’etat partage. Cette section demontre l’entonnoir agentique que le tronc EPITA-IS tient sur la meme taxonomie : FallacyWorkflowPlugin.run_guided_analysis (plugin argumentation_lib._fallacy_workflow_plugin, vendore verbatim depuis ecfd9b9c).
Architecture maitre/esclave : un LLM esclave n’a pour outils que ExplorationPlugin.explore_branch / confirm_fallacy / conclude_no_fallacy (vendore cycle 10). Il ne repond pas librement : il navigue dans l’arbre de la taxonomie. Le cout est borne par MIN_CONFIRM_DEPTH=2, MAX_BRANCHES=4 et des budgets d’appels (cf jsboigeEpita/2025-Epita-Intelligence-Symbolique#1121 et #2157).
Portee 2 (livree par vendoring cycle 10) : les fichiers upstream sont byte-identity sur tronc ecfd9b9c + un shim namespace _epita_vendor_shim qui les re-expose sous leurs noms upstream. Portee 3 (cette cellule) : smoke-test sans execution LLM – instantiation du TaxonomyNavigator sur le CSV taxonomy, navigation, et FAIL-LOUD explicite si on tente run_guided_analysis sans cle API.
Prerequis : la cle API du provider LLM (OpenAI ou Azure OpenAI) doit etre dans OPENAI_API_KEY (ou AZURE_OPENAI_API_KEY + AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_DEPLOYMENT_NAME). Sans cle, la cellule leve RuntimeError immediat et ne replie JAMAIS en silence sur la baseline mots-cles – c’est la regle F de CLAUDE.md (Stop & Repair).
# Cellule [section 6] - Entonnoir taxonomique : smoke-test sans execution LLM## Cette cellule demontre que l'entonnoir du tronc EPITA est bien disponible# dans la shim (vendoring verbatim ecfd9b9c), via le shim namespace# `_epita_vendor_shim`. Elle NE JOUE PAS run_guided_analysis -- cela# necessite une cle API et un endpoint LLM ; sans eux, la cellule FAIL-LOUD# (cf regle F : Stop & Repair, pas de repli silencieux).import csvimport osfrom argumentation_lib import get_data_dir# Le shim namespace deplace les imports upstream# (`from argumentation_analysis.X import Y`) vers les fichiers locaux# (`argumentation_lib._X`) au load-time. Cote import, c'est transparent.import argumentation_lib._epita_vendor_shim # noqa: F401 (effet de bord sys.modules)from argumentation_analysis.agents.utils.taxonomy_navigator import TaxonomyNavigatorcsv_path = get_data_dir() /"argumentum_fallacies_taxonomy.csv"# encoding utf-8-sig : le CSV porte un BOM UTF-8 sur la 1ere colonne (PK)# que DictReader conserve comme cle '\ufeffPK', ce qui casse get_node().with csv_path.open(encoding='utf-8-sig') as f: rows =list(csv.DictReader(f))nav = TaxonomyNavigator(rows)print(f'Taxonomie chargee : {len(rows)} noeuds')roots = nav.get_root_nodes()print(f'racines ({len(roots)}) : {[n["Famille"] for n in roots]}')# Smoke-test de navigation : racine 1, profondeur 1 -> 2 (descente limitée)root1 = nav.get_root_nodes()[0]print(f'Depth=1 racine 1: {root1["Famille"]} ({len(nav.get_children(root1["path"]))} sous-familles directes)')children = nav.get_children(root1['path'])for sub in children[:3]: # 3 premieres sous-familles (suffisant pour la demo) label_sub = sub.get('Sous-Famille') or sub.get('Famille') or sub.get('path', '?') sub_children = nav.get_children(sub['path'])print(f' Depth=2: {label_sub} ({len(sub_children)} sous-sous-familles)')# Verification de la disponibilite de l'API reellefrom argumentation_analysis.plugins.fallacy_workflow_plugin import FallacyWorkflowPluginprint(f'\nFallacyWorkflowPlugin charge : {FallacyWorkflowPlugin.__module__}')print('Architecture disponible : entonnoir pret a tourner avec une cle API.')# FAIL-LOUD si pas de cle API -- le portee 3 n'est pas executee sans elle.ifnot (os.environ.get('OPENAI_API_KEY') or os.environ.get('AZURE_OPENAI_API_KEY')):print('\n[FAIL-LOUD] Pas de cle API detectee (OPENAI_API_KEY / AZURE_OPENAI_API_KEY).')print(' Le squelette est disponible, mais run_guided_analysis ne peut pas tourner.')print(' Poser la cle + relancer cette cellule pour executer l\'entonnoir sur le texte.')print(' Pas de repli silencieux sur la baseline mots-cles (regle F Stop & Repair).')else:print('\n[OK] Cle API detectee -- prete pour run_guided_analysis (non execute par cette cellule).')
Taxonomie chargee : 1408 noeuds
racines (7) : ['Insuffisance', 'Influence', 'Erreur mathématique', 'Erreur de raisonnement', 'Abus de langage', 'Tricherie', 'Obstruction']
Depth=1 racine 1: Insuffisance (3 sous-familles directes)
Depth=2: Argument bâclé (0 sous-sous-familles)
Depth=2: Préjugé (0 sous-sous-familles)
Depth=2: Surinterprétation (0 sous-sous-familles)
FallacyWorkflowPlugin charge : argumentation_lib._fallacy_workflow_plugin
Architecture disponible : entonnoir pret a tourner avec une cle API.
[FAIL-LOUD] Pas de cle API detectee (OPENAI_API_KEY / AZURE_OPENAI_API_KEY).
Le squelette est disponible, mais run_guided_analysis ne peut pas tourner.
Poser la cle + relancer cette cellule pour executer l'entonnoir sur le texte.
Pas de repli silencieux sur la baseline mots-cles (regle F Stop & Repair).
Interpretation : l’entonnoir est disponible, mais n’est pas joue en local
La cellule ci-dessus demontre trois choses :
Le shim fonctionne : from argumentation_analysis.X import Y (syntaxe upstream) reussit, et la classe est bien celle de argumentation_lib._X (vendoring verbatim). C’est le resultat de la portee 2 de #18391 (PR #19287).
La taxonomie est navigable : la meme taxonomie que la section 2 charge deja, plusieurs racines et sous-familles, descente depth=1 -> 2 montre quelques enfants par sous-famille. Le TaxonomyNavigator du tronc EPITA tient la meme taxonomie que la section 2 charge deja.
L’entonnoir est pret mais FAIL-LOUD : sans cle API, la cellule ne JOUE PAS run_guided_analysis ; elle le dit explicitement et arrete. C’est la regle F (Stop & Repair) : pas de repli silencieux sur la baseline mots-cles.
Pour jouer l’entonnoir sur le TEXTE_SYNTHETIQUE de la section 4 : poser OPENAI_API_KEY (ou AZURE_OPENAI_API_KEY + endpoint) et relancer la cellule. Le squelette execute alors run_guided_analysis(texte), qui retourne un objet GuidedAnalysisResult (path confirme, branches explorees, profondeur de confirmation, budget consomme).
Portees 4-5 de #18391 (a servir dans des PRs ulterieurs) : - Portee 4 : comparaison des deux detecteurs (lexique mots-cles vs entonnoir) sur le meme texte synthetique, metriques cote-a-cote. - Portee 5 : pont RhetoricalAnalysisState qui injecte le resultat GuidedAnalysisResult dans l’etat partage, en plus des detections mots-cles de la section 5.